Compare commits

...

24 Commits

Author SHA1 Message Date
TwinProduction
2d3fe9795f Add v3 to module path
Gatus wasn't intended to be used as a library, but I have a use case now.
2021-10-03 21:53:59 -04:00
TwinProduction
d19f564e4e Ensure that tested hour never goes negative 2021-10-03 21:50:58 -04:00
TwinProduction
babe7b0be9 Update Go to 1.17 2021-10-03 15:03:09 -04:00
TwinProduction
dee04945d0 Minor update 2021-10-03 15:03:09 -04:00
TwinProduction
bf455fb7cc Fix issue with under maintenance feature 2021-10-01 02:33:37 -04:00
TwinProduction
dfd2f7943f Fix issue with privileged call on linux 2021-10-01 02:33:16 -04:00
TwinProduction
fece11540b Remove unnecessary rows.Close() calls 2021-09-30 21:19:57 -04:00
TwinProduction
ac43ef4ab7 Refactor some code 2021-09-30 20:56:09 -04:00
TwinProduction
bc25fea1c0 Minor improvements 2021-09-30 20:45:47 -04:00
Carlotronics
30cb7b6ec8 Health check for SSL/TLS services (#177)
* protocol: starttls: add timeout support

Signed-off-by: Charles Decoux <charles@phowork.fr>

* protocol: add ssl support

Signed-off-by: Charles Decoux <charles@phowork.fr>
2021-09-30 16:15:17 -04:00
TwinProduction
289d834587 Change domain 2021-09-28 18:54:40 -04:00
TwinProduction
428e415616 Fix issue with under maintenance 2021-09-27 00:11:42 -04:00
TwinProduction
0d284c2494 Update front-end dependencies 2021-09-25 13:39:54 -04:00
TwinProduction
4a46a5ae9e Rename security.go and security_test.go to config.go and config_test.go 2021-09-22 00:53:13 -04:00
TwinProduction
df3a2016ff Move web and ui configurations in their own packages 2021-09-22 00:47:51 -04:00
TwinProduction
dda83761b5 Minor fix 2021-09-22 00:11:46 -04:00
TwinProduction
882444e0d5 Minor fix 2021-09-22 00:11:22 -04:00
TwinProduction
fa4736c672 Close #74: Add maintenance window 2021-09-22 00:04:51 -04:00
TwinProduction
dc173b29bc Fix indention 2021-09-18 21:43:19 -04:00
TwinProduction
c3a4ce1eb4 Minor update 2021-09-18 13:04:50 -04:00
TwinProduction
044f0454f8 Domain migration 2021-09-18 12:42:11 -04:00
newsr
9bd5c38a96 Add enabled parameter to service (#175)
* feat: Add enabled flag to service
* Add IsEnabled method

Co-authored-by: 1newsr <1newsr@users.noreply.github.com>
2021-09-18 11:52:11 -04:00
TwinProduction
d6b4c2394a Update frontend dependencies 2021-09-16 22:47:03 -04:00
TwinProduction
9fe4678193 Update line separator 2021-09-16 22:35:22 -04:00
126 changed files with 5399 additions and 8903 deletions

2
.gitattributes vendored
View File

@@ -1 +1 @@
* text=lf
* text=auto eol=lf

118
README.md
View File

@@ -14,7 +14,7 @@ the response time, the certificate expiration, the body and many others. The ici
checks can be paired with alerting via Slack, PagerDuty, Discord and even Twilio.
I personally deploy it in my Kubernetes cluster and let it monitor the status of my
core applications: https://status.twinnation.org/
core applications: https://status.twin.sh/
<details>
<summary><b>Quick start</b></summary>
@@ -47,6 +47,7 @@ For more details, see [Usage](#usage)
- [Configuring Twilio alerts](#configuring-twilio-alerts)
- [Configuring custom alerts](#configuring-custom-alerts)
- [Setting a default alert](#setting-a-default-alert)
- [Maintenance](#maintenance)
- [Deployment](#deployment)
- [Docker](#docker)
- [Helm Chart](#helm-chart)
@@ -61,6 +62,7 @@ For more details, see [Usage](#usage)
- [Monitoring a service using ICMP](#monitoring-a-service-using-icmp)
- [Monitoring a service using DNS queries](#monitoring-a-service-using-dns-queries)
- [Monitoring a service using STARTTLS](#monitoring-a-service-using-starttls)
- [Monitoring a service using TLS](#monitoring-a-service-using-tls)
- [Basic authentication](#basic-authentication)
- [disable-monitoring-lock](#disable-monitoring-lock)
- [Reloading configuration on the fly](#reloading-configuration-on-the-fly)
@@ -101,7 +103,7 @@ The main features of Gatus are:
- **Alerting**: While having a pretty visual dashboard is useful to keep track of the state of your application(s), you probably don't want to stare at it all day. Thus, notifications via Slack, Mattermost, Messagebird, PagerDuty, Twilio and Teams are supported out of the box with the ability to configure a custom alerting provider for any needs you might have, whether it be a different provider or a custom application that manages automated rollbacks.
- **Metrics**
- **Low resource consumption**: As with most Go applications, the resource footprint that this application requires is negligibly small.
- **[Badges](#badges)**: ![Uptime 7d](https://status.twinnation.org/api/v1/services/core_website-external/uptimes/7d/badge.svg) ![Response time 24h](https://status.twinnation.org/api/v1/services/core_website-external/response-times/24h/badge.svg)
- **[Badges](#badges)**: ![Uptime 7d](https://status.twin.sh/api/v1/services/core_website-external/uptimes/7d/badge.svg) ![Response time 24h](https://status.twin.sh/api/v1/services/core_website-external/response-times/24h/badge.svg)
## Usage
@@ -114,8 +116,8 @@ Here's a simple example:
```yaml
metrics: true # Whether to expose metrics at /metrics
services:
- name: twinnation # Name of your service, can be anything
url: "https://twinnation.org/health"
- name: website # Name of your service, can be anything
url: "https://twin.sh/health"
interval: 30s # Duration to wait between every status check (default: 60s)
conditions:
- "[STATUS] == 200" # Status must be 200
@@ -144,6 +146,7 @@ If you want to test it locally, see [Docker](#docker).
| `metrics` | Whether to expose metrics at /metrics. | `false` |
| `storage` | [Storage configuration](#storage) | `{}` |
| `services` | List of services to monitor. | Required `[]` |
| `services[].enabled` | Whether to enable the service. | `true` |
| `services[].name` | Name of the service. Can be anything. | Required `""` |
| `services[].group` | Group name. Used to group multiple services together on the dashboard. <br />See [Service groups](#service-groups). | `""` |
| `services[].url` | URL to send the request to. | Required `""` |
@@ -281,8 +284,8 @@ Note that this configuration is only available under `services[]`, `alerting.mat
Here's an example with the client configuration under `service[]`:
```yaml
services:
- name: twinnation
url: "https://twinnation.org/health"
- name: website
url: "https://twin.sh/health"
client:
insecure: false
ignore-redirect: false
@@ -325,8 +328,8 @@ alerting:
webhook-url: "https://discord.com/api/webhooks/**********/**********"
services:
- name: twinnation
url: "https://twinnation.org/health"
- name: website
url: "https://twin.sh/health"
interval: 30s
conditions:
- "[STATUS] == 200"
@@ -356,8 +359,8 @@ alerting:
insecure: true
services:
- name: twinnation
url: "https://twinnation.org/health"
- name: website
url: "https://twin.sh/health"
interval: 30s
conditions:
- "[STATUS] == 200"
@@ -392,9 +395,9 @@ alerting:
originator: "31619191918"
recipients: "31619191919,31619191920"
services:
- name: twinnation
- name: website
interval: 30s
url: "https://twinnation.org/health"
url: "https://twin.sh/health"
conditions:
- "[STATUS] == 200"
- "[BODY].status == UP"
@@ -426,8 +429,8 @@ alerting:
integration-key: "********************************"
services:
- name: twinnation
url: "https://twinnation.org/health"
- name: website
url: "https://twin.sh/health"
interval: 30s
conditions:
- "[STATUS] == 200"
@@ -456,8 +459,8 @@ alerting:
webhook-url: "https://hooks.slack.com/services/**********/**********/**********"
services:
- name: twinnation
url: "https://twinnation.org/health"
- name: website
url: "https://twin.sh/health"
interval: 30s
conditions:
- "[STATUS] == 200"
@@ -493,8 +496,8 @@ alerting:
webhook-url: "https://********.webhook.office.com/webhookb2/************"
services:
- name: twinnation
url: "https://twinnation.org/health"
- name: website
url: "https://twin.sh/health"
interval: 30s
conditions:
- "[STATUS] == 200"
@@ -526,8 +529,8 @@ alerting:
id: "0123456789"
services:
- name: twinnation
url: "https://twinnation.org/health"
- name: website
url: "https://twin.sh/health"
interval: 30s
conditions:
- "[STATUS] == 200"
@@ -562,9 +565,9 @@ alerting:
to: "+1-234-567-8901"
services:
- name: twinnation
- name: website
interval: 30s
url: "https://twinnation.org/health"
url: "https://twin.sh/health"
conditions:
- "[STATUS] == 200"
- "[BODY].status == UP"
@@ -615,8 +618,8 @@ alerting:
"text": "[ALERT_TRIGGERED_OR_RESOLVED]: [SERVICE_NAME] - [ALERT_DESCRIPTION]"
}
services:
- name: twinnation
url: "https://twinnation.org/health"
- name: website
url: "https://twin.sh/health"
interval: 30s
conditions:
- "[STATUS] == 200"
@@ -735,6 +738,37 @@ services:
- type: pagerduty
```
### Maintenance
If you have maintenance windows, you may not want to be annoyed by alerts.
To do that, you'll have to use the maintenance configuration:
| Parameter | Description | Default |
|:----------------------- |:----------------------------------------------------------------------------- |:--------------- |
| `maintenance.enabled` | Whether the maintenance period is enabled | `true` |
| `maintenance.start` | Time at which the maintenance window starts in `hh:mm` format (e.g. `23:00`) | Required `""` |
| `maintenance.duration` | Duration of the maintenance window (e.g. `1h`, `30m`) | Required `""` |
| `maintenance.every` | Days on which the maintenance period applies (e.g. `[Monday, Thursday]`).<br />If left empty, the maintenance window applies every day | `[]` |
**Note that the maintenance configuration uses UTC.**
Here's an example:
```yaml
maintenance:
start: 23:00
duration: 1h
every: [Monday, Thursday]
```
Note that you can also specify each day on separate lines:
```yaml
maintenance:
start: 23:00
duration: 1h
every:
- Monday
- Thursday
```
## Deployment
Many examples can be found in the [examples](examples) folder, but this section will focus on the most popular ways of deploying Gatus.
@@ -930,6 +964,24 @@ services:
- name: starttls-smtp-example
url: "starttls://smtp.gmail.com:587"
interval: 30m
client:
timeout: 5s
conditions:
- "[CONNECTED] == true"
- "[CERTIFICATE_EXPIRATION] > 48h"
```
### Monitoring a service using TLS
Monitoring services using SSL/TLS encryption, such as LDAP over TLS, can help
detecting certificate expiration:
```yaml
services:
- name: tls-ldaps-example
url: "tls://ldap.example.com:636"
interval: 30m
client:
timeout: 5s
conditions:
- "[CONNECTED] == true"
- "[CERTIFICATE_EXPIRATION] > 48h"
@@ -1047,9 +1099,9 @@ web:
### Badges
### Uptime
![Uptime 1h](https://status.twinnation.org/api/v1/services/core_website-external/uptimes/1h/badge.svg)
![Uptime 24h](https://status.twinnation.org/api/v1/services/core_website-external/uptimes/24h/badge.svg)
![Uptime 7d](https://status.twinnation.org/api/v1/services/core_website-external/uptimes/7d/badge.svg)
![Uptime 1h](https://status.twin.sh/api/v1/services/core_website-external/uptimes/1h/badge.svg)
![Uptime 24h](https://status.twin.sh/api/v1/services/core_website-external/uptimes/24h/badge.svg)
![Uptime 7d](https://status.twin.sh/api/v1/services/core_website-external/uptimes/7d/badge.svg)
Gatus can automatically generate a SVG badge for one of your monitored services.
This allows you to put badges in your individual services' README or even create your own status page, if you
@@ -1074,15 +1126,15 @@ https://example.com/api/v1/services/_frontend/uptimes/7d/badge.svg
```
Example:
```
![Uptime 24h](https://status.twinnation.org/api/v1/services/core_website-external/uptimes/24h/badge.svg)
![Uptime 24h](https://status.twin.sh/api/v1/services/core_website-external/uptimes/24h/badge.svg)
```
If you'd like to see a visual example of each badges available, you can simply navigate to the service's detail page.
### Response time
![Response time 1h](https://status.twinnation.org/api/v1/services/core_website-external/response-times/1h/badge.svg)
![Response time 24h](https://status.twinnation.org/api/v1/services/core_website-external/response-times/24h/badge.svg)
![Response time 7d](https://status.twinnation.org/api/v1/services/core_website-external/response-times/7d/badge.svg)
![Response time 1h](https://status.twin.sh/api/v1/services/core_website-external/response-times/1h/badge.svg)
![Response time 24h](https://status.twin.sh/api/v1/services/core_website-external/response-times/24h/badge.svg)
![Response time 7d](https://status.twin.sh/api/v1/services/core_website-external/response-times/7d/badge.svg)
The endpoint to generate a badge is the following:
```
@@ -1100,13 +1152,13 @@ All services are available via a GET request to the following endpoint:
```
/api/v1/services/statuses
````
Example: https://status.twinnation.org/api/v1/services/statuses
Example: https://status.twin.sh/api/v1/services/statuses
Specific services can also be queried by using the following pattern:
```
/api/v1/services/{group}_{service}/statuses
```
Example: https://status.twinnation.org/api/v1/services/core_website-home/statuses
Example: https://status.twin.sh/api/v1/services/core_website-home/statuses
Gzip compression will be used if the `Accept-Encoding` HTTP header contains `gzip`.

View File

@@ -1,17 +1,17 @@
package alerting
import (
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/alerting/provider"
"github.com/TwinProduction/gatus/alerting/provider/custom"
"github.com/TwinProduction/gatus/alerting/provider/discord"
"github.com/TwinProduction/gatus/alerting/provider/mattermost"
"github.com/TwinProduction/gatus/alerting/provider/messagebird"
"github.com/TwinProduction/gatus/alerting/provider/pagerduty"
"github.com/TwinProduction/gatus/alerting/provider/slack"
"github.com/TwinProduction/gatus/alerting/provider/teams"
"github.com/TwinProduction/gatus/alerting/provider/telegram"
"github.com/TwinProduction/gatus/alerting/provider/twilio"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/alerting/provider"
"github.com/TwinProduction/gatus/v3/alerting/provider/custom"
"github.com/TwinProduction/gatus/v3/alerting/provider/discord"
"github.com/TwinProduction/gatus/v3/alerting/provider/mattermost"
"github.com/TwinProduction/gatus/v3/alerting/provider/messagebird"
"github.com/TwinProduction/gatus/v3/alerting/provider/pagerduty"
"github.com/TwinProduction/gatus/v3/alerting/provider/slack"
"github.com/TwinProduction/gatus/v3/alerting/provider/teams"
"github.com/TwinProduction/gatus/v3/alerting/provider/telegram"
"github.com/TwinProduction/gatus/v3/alerting/provider/twilio"
)
// Config is the configuration for alerting providers

View File

@@ -9,9 +9,9 @@ import (
"os"
"strings"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/client"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/client"
"github.com/TwinProduction/gatus/v3/core"
)
// AlertProvider is the configuration necessary for sending an alert using a custom HTTP request

View File

@@ -4,8 +4,8 @@ import (
"io/ioutil"
"testing"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/core"
)
func TestAlertProvider_IsValid(t *testing.T) {

View File

@@ -4,9 +4,9 @@ import (
"fmt"
"net/http"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/alerting/provider/custom"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/alerting/provider/custom"
"github.com/TwinProduction/gatus/v3/core"
)
// AlertProvider is the configuration necessary for sending an alert using Discord

View File

@@ -6,8 +6,8 @@ import (
"strings"
"testing"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/core"
)
func TestAlertProvider_IsValid(t *testing.T) {

View File

@@ -4,10 +4,10 @@ import (
"fmt"
"net/http"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/alerting/provider/custom"
"github.com/TwinProduction/gatus/client"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/alerting/provider/custom"
"github.com/TwinProduction/gatus/v3/client"
"github.com/TwinProduction/gatus/v3/core"
)
// AlertProvider is the configuration necessary for sending an alert using Mattermost

View File

@@ -6,8 +6,8 @@ import (
"strings"
"testing"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/core"
)
func TestAlertProvider_IsValid(t *testing.T) {

View File

@@ -4,9 +4,9 @@ import (
"fmt"
"net/http"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/alerting/provider/custom"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/alerting/provider/custom"
"github.com/TwinProduction/gatus/v3/core"
)
const (
@@ -37,7 +37,6 @@ func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, aler
} else {
message = fmt.Sprintf("TRIGGERED: %s - %s", service.Name, alert.GetDescription())
}
return &custom.AlertProvider{
URL: restAPIURL,
Method: http.MethodPost,

View File

@@ -6,8 +6,8 @@ import (
"strings"
"testing"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/core"
)
func TestMessagebirdAlertProvider_IsValid(t *testing.T) {

View File

@@ -4,9 +4,9 @@ import (
"fmt"
"net/http"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/alerting/provider/custom"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/alerting/provider/custom"
"github.com/TwinProduction/gatus/v3/core"
)
const (

View File

@@ -6,8 +6,8 @@ import (
"strings"
"testing"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/core"
)
func TestAlertProvider_IsValid(t *testing.T) {

View File

@@ -1,17 +1,17 @@
package provider
import (
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/alerting/provider/custom"
"github.com/TwinProduction/gatus/alerting/provider/discord"
"github.com/TwinProduction/gatus/alerting/provider/mattermost"
"github.com/TwinProduction/gatus/alerting/provider/messagebird"
"github.com/TwinProduction/gatus/alerting/provider/pagerduty"
"github.com/TwinProduction/gatus/alerting/provider/slack"
"github.com/TwinProduction/gatus/alerting/provider/teams"
"github.com/TwinProduction/gatus/alerting/provider/telegram"
"github.com/TwinProduction/gatus/alerting/provider/twilio"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/alerting/provider/custom"
"github.com/TwinProduction/gatus/v3/alerting/provider/discord"
"github.com/TwinProduction/gatus/v3/alerting/provider/mattermost"
"github.com/TwinProduction/gatus/v3/alerting/provider/messagebird"
"github.com/TwinProduction/gatus/v3/alerting/provider/pagerduty"
"github.com/TwinProduction/gatus/v3/alerting/provider/slack"
"github.com/TwinProduction/gatus/v3/alerting/provider/teams"
"github.com/TwinProduction/gatus/v3/alerting/provider/telegram"
"github.com/TwinProduction/gatus/v3/alerting/provider/twilio"
"github.com/TwinProduction/gatus/v3/core"
)
// AlertProvider is the interface that each providers should implement

View File

@@ -3,7 +3,7 @@ package provider
import (
"testing"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/v3/alerting/alert"
)
func TestParseWithDefaultAlert(t *testing.T) {

View File

@@ -4,9 +4,9 @@ import (
"fmt"
"net/http"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/alerting/provider/custom"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/alerting/provider/custom"
"github.com/TwinProduction/gatus/v3/core"
)
// AlertProvider is the configuration necessary for sending an alert using Slack

View File

@@ -6,8 +6,8 @@ import (
"strings"
"testing"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/core"
)
func TestAlertProvider_IsValid(t *testing.T) {

View File

@@ -4,9 +4,9 @@ import (
"fmt"
"net/http"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/alerting/provider/custom"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/alerting/provider/custom"
"github.com/TwinProduction/gatus/v3/core"
)
// AlertProvider is the configuration necessary for sending an alert using Teams

View File

@@ -6,8 +6,8 @@ import (
"strings"
"testing"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/core"
)
func TestAlertProvider_IsValid(t *testing.T) {

View File

@@ -4,9 +4,9 @@ import (
"fmt"
"net/http"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/alerting/provider/custom"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/alerting/provider/custom"
"github.com/TwinProduction/gatus/v3/core"
)
// AlertProvider is the configuration necessary for sending an alert using Telegram

View File

@@ -7,8 +7,8 @@ import (
"strings"
"testing"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/core"
)
func TestAlertProvider_IsValid(t *testing.T) {

View File

@@ -6,9 +6,9 @@ import (
"net/http"
"net/url"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/alerting/provider/custom"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/alerting/provider/custom"
"github.com/TwinProduction/gatus/v3/core"
)
// AlertProvider is the configuration necessary for sending an alert using Twilio

View File

@@ -5,8 +5,8 @@ import (
"strings"
"testing"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/core"
)
func TestTwilioAlertProvider_IsValid(t *testing.T) {

View File

@@ -38,7 +38,11 @@ func CanPerformStartTLS(address string, config *Config) (connected bool, certifi
if len(hostAndPort) != 2 {
return false, nil, errors.New("invalid address for starttls, format must be host:port")
}
smtpClient, err := smtp.Dial(address)
connection, err := net.DialTimeout("tcp", address, config.Timeout)
if err != nil {
return
}
smtpClient, err := smtp.NewClient(connection, hostAndPort[0])
if err != nil {
return
}
@@ -57,6 +61,20 @@ func CanPerformStartTLS(address string, config *Config) (connected bool, certifi
return true, certificate, nil
}
// CanPerformTLS checks whether a connection can be established to an address using the TLS protocol
func CanPerformTLS(address string, config *Config) (connected bool, certificate *x509.Certificate, err error) {
connection, err := tls.DialWithDialer(&net.Dialer{Timeout: config.Timeout}, "tcp", address, nil)
if err != nil {
return
}
defer connection.Close()
verifiedChains := connection.ConnectionState().VerifiedChains
if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 {
return
}
return true, verifiedChains[0][0], nil
}
// Ping checks if an address can be pinged and returns the round-trip time if the address can be pinged
//
// Note that this function takes at least 100ms, even if the address is 127.0.0.1
@@ -67,9 +85,9 @@ func Ping(address string, config *Config) (bool, time.Duration) {
}
pinger.Count = 1
pinger.Timeout = config.Timeout
// Set the pinger's privileged mode to true for every operating system except darwin
// Set the pinger's privileged mode to true for windows
// https://github.com/TwinProduction/gatus/issues/132
pinger.SetPrivileged(runtime.GOOS != "darwin")
pinger.SetPrivileged(runtime.GOOS == "windows")
err = pinger.Run()
if err != nil {
return false, 0

View File

@@ -91,6 +91,56 @@ func TestCanPerformStartTLS(t *testing.T) {
}
}
func TestCanPerformTLS(t *testing.T) {
type args struct {
address string
insecure bool
}
tests := []struct {
name string
args args
wantConnected bool
wantErr bool
}{
{
name: "invalid address",
args: args{
address: "test",
},
wantConnected: false,
wantErr: true,
},
{
name: "error dial",
args: args{
address: "test:1234",
},
wantConnected: false,
wantErr: true,
},
{
name: "valid tls",
args: args{
address: "smtp.gmail.com:465",
},
wantConnected: true,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
connected, _, err := CanPerformTLS(tt.args.address, &Config{Insecure: tt.args.insecure, Timeout: 5 * time.Second})
if (err != nil) != tt.wantErr {
t.Errorf("CanPerformTLS() err=%v, wantErr=%v", err, tt.wantErr)
return
}
if connected != tt.wantConnected {
t.Errorf("CanPerformTLS() connected=%v, wantConnected=%v", connected, tt.wantConnected)
}
})
}
}
func TestCanCreateTCPConnection(t *testing.T) {
if CanCreateTCPConnection("127.0.0.1", &Config{Timeout: 5 * time.Second}) {
t.Error("should've failed, because there's no port in the address")

View File

@@ -1,7 +1,7 @@
services:
- name: front-end
group: core
url: "https://twinnation.org/health"
url: "https://twin.sh/health"
interval: 1m
conditions:
- "[STATUS] == 200"

View File

@@ -7,12 +7,15 @@ import (
"os"
"time"
"github.com/TwinProduction/gatus/alerting"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/alerting/provider"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/security"
"github.com/TwinProduction/gatus/storage"
"github.com/TwinProduction/gatus/v3/alerting"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/alerting/provider"
"github.com/TwinProduction/gatus/v3/config/maintenance"
"github.com/TwinProduction/gatus/v3/config/ui"
"github.com/TwinProduction/gatus/v3/config/web"
"github.com/TwinProduction/gatus/v3/core"
"github.com/TwinProduction/gatus/v3/security"
"github.com/TwinProduction/gatus/v3/storage"
"gopkg.in/yaml.v2"
)
@@ -24,12 +27,6 @@ const (
// DefaultFallbackConfigurationFilePath is the default fallback path that will be used to search for the
// configuration file if DefaultConfigurationFilePath didn't work
DefaultFallbackConfigurationFilePath = "config/config.yml"
// DefaultAddress is the default address the service will bind to
DefaultAddress = "0.0.0.0"
// DefaultPort is the default port the service will listen on
DefaultPort = 8080
)
var (
@@ -41,10 +38,6 @@ var (
// ErrInvalidSecurityConfig is an error returned when the security configuration is invalid
ErrInvalidSecurityConfig = errors.New("invalid security configuration")
// StaticFolder is the path to the location of the static folder from the root path of the project
// The only reason this is exposed is to allow running tests from a different path than the root path of the project
StaticFolder = "./web/static"
)
// Config is the main configuration structure
@@ -77,10 +70,13 @@ type Config struct {
Storage *storage.Config `yaml:"storage"`
// Web is the configuration for the web listener
Web *WebConfig `yaml:"web"`
Web *web.Config `yaml:"web"`
// UI is the configuration for the UI
UI *UIConfig `yaml:"ui"`
UI *ui.Config `yaml:"ui"`
// Maintenance is the configuration for creating a maintenance window in which no alerts are sent
Maintenance *maintenance.Config `yaml:"maintenance"`
filePath string // path to the file from which config was loaded from
lastFileModTime time.Time // last modification time
@@ -172,6 +168,9 @@ func parseAndValidateConfigBytes(yamlBytes []byte) (config *Config, err error) {
if err := validateUIConfig(config); err != nil {
return nil, err
}
if err := validateMaintenanceConfig(config); err != nil {
return nil, err
}
if err := validateStorageConfig(config); err != nil {
return nil, err
}
@@ -201,11 +200,22 @@ func validateStorageConfig(config *Config) error {
return nil
}
func validateMaintenanceConfig(config *Config) error {
if config.Maintenance == nil {
config.Maintenance = maintenance.GetDefaultConfig()
} else {
if err := config.Maintenance.ValidateAndSetDefaults(); err != nil {
return err
}
}
return nil
}
func validateUIConfig(config *Config) error {
if config.UI == nil {
config.UI = GetDefaultUIConfig()
config.UI = ui.GetDefaultConfig()
} else {
if err := config.UI.validateAndSetDefaults(); err != nil {
if err := config.UI.ValidateAndSetDefaults(); err != nil {
return err
}
}
@@ -214,9 +224,9 @@ func validateUIConfig(config *Config) error {
func validateWebConfig(config *Config) error {
if config.Web == nil {
config.Web = GetDefaultWebConfig()
config.Web = web.GetDefaultConfig()
} else {
return config.Web.validateAndSetDefaults()
return config.Web.ValidateAndSetDefaults()
}
return nil
}

View File

@@ -5,19 +5,21 @@ import (
"testing"
"time"
"github.com/TwinProduction/gatus/alerting"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/alerting/provider/custom"
"github.com/TwinProduction/gatus/alerting/provider/discord"
"github.com/TwinProduction/gatus/alerting/provider/mattermost"
"github.com/TwinProduction/gatus/alerting/provider/messagebird"
"github.com/TwinProduction/gatus/alerting/provider/pagerduty"
"github.com/TwinProduction/gatus/alerting/provider/slack"
"github.com/TwinProduction/gatus/alerting/provider/teams"
"github.com/TwinProduction/gatus/alerting/provider/telegram"
"github.com/TwinProduction/gatus/alerting/provider/twilio"
"github.com/TwinProduction/gatus/client"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/alerting"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/alerting/provider/custom"
"github.com/TwinProduction/gatus/v3/alerting/provider/discord"
"github.com/TwinProduction/gatus/v3/alerting/provider/mattermost"
"github.com/TwinProduction/gatus/v3/alerting/provider/messagebird"
"github.com/TwinProduction/gatus/v3/alerting/provider/pagerduty"
"github.com/TwinProduction/gatus/v3/alerting/provider/slack"
"github.com/TwinProduction/gatus/v3/alerting/provider/teams"
"github.com/TwinProduction/gatus/v3/alerting/provider/telegram"
"github.com/TwinProduction/gatus/v3/alerting/provider/twilio"
"github.com/TwinProduction/gatus/v3/client"
"github.com/TwinProduction/gatus/v3/config/ui"
"github.com/TwinProduction/gatus/v3/config/web"
"github.com/TwinProduction/gatus/v3/core"
)
func TestLoadFileThatDoesNotExist(t *testing.T) {
@@ -36,18 +38,23 @@ func TestLoadDefaultConfigurationFile(t *testing.T) {
func TestParseAndValidateConfigBytes(t *testing.T) {
file := t.TempDir() + "/test.db"
StaticFolder = "../web/static"
ui.StaticFolder = "../web/static"
defer func() {
StaticFolder = "./web/static"
ui.StaticFolder = "./web/static"
}()
config, err := parseAndValidateConfigBytes([]byte(fmt.Sprintf(`
storage:
file: %s
maintenance:
enabled: true
start: 00:00
duration: 4h
every: [Monday, Thursday]
ui:
title: Test
services:
- name: twinnation
url: https://twinnation.org/health
- name: website
url: https://twin.sh/health
interval: 15s
conditions:
- "[STATUS] == 200"
@@ -79,12 +86,15 @@ services:
if config.UI == nil || config.UI.Title != "Test" {
t.Error("Expected Config.UI.Title to be Test")
}
if mc := config.Maintenance; mc == nil || mc.Start != "00:00" || !mc.IsEnabled() || mc.Duration != 4*time.Hour || len(mc.Every) != 2 {
t.Error("Expected Config.Maintenance to be configured properly")
}
if len(config.Services) != 3 {
t.Error("Should have returned two services")
}
if config.Services[0].URL != "https://twinnation.org/health" {
t.Errorf("URL should have been %s", "https://twinnation.org/health")
if config.Services[0].URL != "https://twin.sh/health" {
t.Errorf("URL should have been %s", "https://twin.sh/health")
}
if config.Services[0].Method != "GET" {
t.Errorf("Method should have been %s (default)", "GET")
@@ -153,8 +163,8 @@ services:
func TestParseAndValidateConfigBytesDefault(t *testing.T) {
config, err := parseAndValidateConfigBytes([]byte(`
services:
- name: twinnation
url: https://twinnation.org/health
- name: website
url: https://twin.sh/health
conditions:
- "[STATUS] == 200"
`))
@@ -167,14 +177,14 @@ services:
if config.Metrics {
t.Error("Metrics should've been false by default")
}
if config.Web.Address != DefaultAddress {
t.Errorf("Bind address should have been %s, because it is the default value", DefaultAddress)
if config.Web.Address != web.DefaultAddress {
t.Errorf("Bind address should have been %s, because it is the default value", web.DefaultAddress)
}
if config.Web.Port != DefaultPort {
t.Errorf("Port should have been %d, because it is the default value", DefaultPort)
if config.Web.Port != web.DefaultPort {
t.Errorf("Port should have been %d, because it is the default value", web.DefaultPort)
}
if config.Services[0].URL != "https://twinnation.org/health" {
t.Errorf("URL should have been %s", "https://twinnation.org/health")
if config.Services[0].URL != "https://twin.sh/health" {
t.Errorf("URL should have been %s", "https://twin.sh/health")
}
if config.Services[0].Interval != 60*time.Second {
t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second)
@@ -195,8 +205,8 @@ func TestParseAndValidateConfigBytesWithAddress(t *testing.T) {
web:
address: 127.0.0.1
services:
- name: twinnation
url: https://twinnation.org/actuator/health
- name: website
url: https://twin.sh/actuator/health
conditions:
- "[STATUS] == 200"
`))
@@ -209,8 +219,8 @@ services:
if config.Metrics {
t.Error("Metrics should've been false by default")
}
if config.Services[0].URL != "https://twinnation.org/actuator/health" {
t.Errorf("URL should have been %s", "https://twinnation.org/actuator/health")
if config.Services[0].URL != "https://twin.sh/actuator/health" {
t.Errorf("URL should have been %s", "https://twin.sh/actuator/health")
}
if config.Services[0].Interval != 60*time.Second {
t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second)
@@ -218,8 +228,8 @@ services:
if config.Web.Address != "127.0.0.1" {
t.Errorf("Bind address should have been %s, because it is specified in config", "127.0.0.1")
}
if config.Web.Port != DefaultPort {
t.Errorf("Port should have been %d, because it is the default value", DefaultPort)
if config.Web.Port != web.DefaultPort {
t.Errorf("Port should have been %d, because it is the default value", web.DefaultPort)
}
}
@@ -228,8 +238,8 @@ func TestParseAndValidateConfigBytesWithPort(t *testing.T) {
web:
port: 12345
services:
- name: twinnation
url: https://twinnation.org/health
- name: website
url: https://twin.sh/health
conditions:
- "[STATUS] == 200"
`))
@@ -242,14 +252,14 @@ services:
if config.Metrics {
t.Error("Metrics should've been false by default")
}
if config.Services[0].URL != "https://twinnation.org/health" {
t.Errorf("URL should have been %s", "https://twinnation.org/health")
if config.Services[0].URL != "https://twin.sh/health" {
t.Errorf("URL should have been %s", "https://twin.sh/health")
}
if config.Services[0].Interval != 60*time.Second {
t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second)
}
if config.Web.Address != DefaultAddress {
t.Errorf("Bind address should have been %s, because it is the default value", DefaultAddress)
if config.Web.Address != web.DefaultAddress {
t.Errorf("Bind address should have been %s, because it is the default value", web.DefaultAddress)
}
if config.Web.Port != 12345 {
t.Errorf("Port should have been %d, because it is specified in config", 12345)
@@ -262,8 +272,8 @@ web:
port: 12345
address: 127.0.0.1
services:
- name: twinnation
url: https://twinnation.org/health
- name: website
url: https://twin.sh/health
conditions:
- "[STATUS] == 200"
`))
@@ -276,8 +286,8 @@ services:
if config.Metrics {
t.Error("Metrics should've been false by default")
}
if config.Services[0].URL != "https://twinnation.org/health" {
t.Errorf("URL should have been %s", "https://twinnation.org/health")
if config.Services[0].URL != "https://twin.sh/health" {
t.Errorf("URL should have been %s", "https://twin.sh/health")
}
if config.Services[0].Interval != 60*time.Second {
t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second)
@@ -296,8 +306,8 @@ web:
port: 65536
address: 127.0.0.1
services:
- name: twinnation
url: https://twinnation.org/health
- name: website
url: https://twin.sh/health
conditions:
- "[STATUS] == 200"
`))
@@ -310,8 +320,8 @@ func TestParseAndValidateConfigBytesWithMetricsAndCustomUserAgentHeader(t *testi
config, err := parseAndValidateConfigBytes([]byte(`
metrics: true
services:
- name: twinnation
url: https://twinnation.org/health
- name: website
url: https://twin.sh/health
headers:
User-Agent: Test/2.0
conditions:
@@ -326,17 +336,17 @@ services:
if !config.Metrics {
t.Error("Metrics should have been true")
}
if config.Services[0].URL != "https://twinnation.org/health" {
t.Errorf("URL should have been %s", "https://twinnation.org/health")
if config.Services[0].URL != "https://twin.sh/health" {
t.Errorf("URL should have been %s", "https://twin.sh/health")
}
if config.Services[0].Interval != 60*time.Second {
t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second)
}
if config.Web.Address != DefaultAddress {
t.Errorf("Bind address should have been %s, because it is the default value", DefaultAddress)
if config.Web.Address != web.DefaultAddress {
t.Errorf("Bind address should have been %s, because it is the default value", web.DefaultAddress)
}
if config.Web.Port != DefaultPort {
t.Errorf("Port should have been %d, because it is the default value", DefaultPort)
if config.Web.Port != web.DefaultPort {
t.Errorf("Port should have been %d, because it is the default value", web.DefaultPort)
}
if userAgent := config.Services[0].Headers["User-Agent"]; userAgent != "Test/2.0" {
t.Errorf("User-Agent should've been %s, got %s", "Test/2.0", userAgent)
@@ -350,8 +360,8 @@ web:
address: 192.168.0.1
port: 9090
services:
- name: twinnation
url: https://twinnation.org/health
- name: website
url: https://twin.sh/health
conditions:
- "[STATUS] == 200"
`))
@@ -370,8 +380,8 @@ services:
if config.Web.Port != 9090 {
t.Errorf("Port should have been %d, because it is specified in config", 9090)
}
if config.Services[0].URL != "https://twinnation.org/health" {
t.Errorf("URL should have been %s", "https://twinnation.org/health")
if config.Services[0].URL != "https://twin.sh/health" {
t.Errorf("URL should have been %s", "https://twin.sh/health")
}
if config.Services[0].Interval != 60*time.Second {
t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second)
@@ -427,8 +437,8 @@ alerting:
webhook-url: "http://example.com"
services:
- name: twinnation
url: https://twinnation.org/health
- name: website
url: https://twin.sh/health
alerts:
- type: slack
enabled: true
@@ -471,8 +481,8 @@ services:
if len(config.Services) != 1 {
t.Error("There should've been 1 service")
}
if config.Services[0].URL != "https://twinnation.org/health" {
t.Errorf("URL should have been %s", "https://twinnation.org/health")
if config.Services[0].URL != "https://twin.sh/health" {
t.Errorf("URL should have been %s", "https://twin.sh/health")
}
if config.Services[0].Interval != 60*time.Second {
t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second)
@@ -633,8 +643,8 @@ alerting:
enabled: true
services:
- name: twinnation
url: https://twinnation.org/health
- name: website
url: https://twin.sh/health
alerts:
- type: slack
- type: pagerduty
@@ -748,8 +758,8 @@ services:
if len(config.Services) != 1 {
t.Error("There should've been 1 service")
}
if config.Services[0].URL != "https://twinnation.org/health" {
t.Errorf("URL should have been %s", "https://twinnation.org/health")
if config.Services[0].URL != "https://twin.sh/health" {
t.Errorf("URL should have been %s", "https://twin.sh/health")
}
if config.Services[0].Interval != 60*time.Second {
t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second)
@@ -871,8 +881,8 @@ alerting:
description: "description"
services:
- name: twinnation
url: https://twinnation.org/health
- name: website
url: https://twin.sh/health
alerts:
- type: slack
failure-threshold: 10
@@ -946,8 +956,8 @@ alerting:
pagerduty:
integration-key: "INVALID_KEY"
services:
- name: twinnation
url: https://twinnation.org/health
- name: website
url: https://twin.sh/health
alerts:
- type: pagerduty
conditions:
@@ -980,8 +990,8 @@ alerting:
"text": "[ALERT_TRIGGERED_OR_RESOLVED]: [SERVICE_NAME] - [ALERT_DESCRIPTION]"
}
services:
- name: twinnation
url: https://twinnation.org/health
- name: website
url: https://twin.sh/health
alerts:
- type: custom
conditions:
@@ -1025,8 +1035,8 @@ alerting:
insecure: true
body: "[ALERT_TRIGGERED_OR_RESOLVED]: [SERVICE_NAME] - [ALERT_DESCRIPTION]"
services:
- name: twinnation
url: https://twinnation.org/health
- name: website
url: https://twin.sh/health
alerts:
- type: custom
conditions:
@@ -1065,8 +1075,8 @@ alerting:
url: "https://example.com"
body: "[ALERT_TRIGGERED_OR_RESOLVED]: [SERVICE_NAME] - [ALERT_DESCRIPTION]"
services:
- name: twinnation
url: https://twinnation.org/health
- name: website
url: https://twin.sh/health
alerts:
- type: custom
conditions:
@@ -1099,7 +1109,7 @@ func TestParseAndValidateConfigBytesWithInvalidServiceName(t *testing.T) {
_, err := parseAndValidateConfigBytes([]byte(`
services:
- name: ""
url: https://twinnation.org/health
url: https://twin.sh/health
conditions:
- "[STATUS] == 200"
`))
@@ -1145,8 +1155,8 @@ security:
username: "admin"
password-sha512: "invalid-sha512-hash"
services:
- name: twinnation
url: https://twinnation.org/health
- name: website
url: https://twin.sh/health
conditions:
- "[STATUS] == 200"
`))
@@ -1164,8 +1174,8 @@ security:
username: "%s"
password-sha512: "%s"
services:
- name: twinnation
url: https://twinnation.org/health
- name: website
url: https://twin.sh/health
conditions:
- "[STATUS] == 200"
`, expectedUsername, expectedPasswordHash)))

View File

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

View File

@@ -0,0 +1,226 @@
package maintenance
import (
"errors"
"fmt"
"strconv"
"testing"
"time"
)
func TestGetDefaultConfig(t *testing.T) {
if *GetDefaultConfig().Enabled {
t.Fatal("expected default config to be disabled by default")
}
}
func TestConfig_ValidateAndSetDefaults(t *testing.T) {
yes, no := true, false
scenarios := []struct {
name string
cfg *Config
expectedError error
}{
{
name: "nil",
cfg: nil,
expectedError: nil,
},
{
name: "disabled",
cfg: &Config{
Enabled: &no,
},
expectedError: nil,
},
{
name: "invalid-day",
cfg: &Config{
Every: []string{"invalid-day"},
},
expectedError: errInvalidDayName,
},
{
name: "invalid-day",
cfg: &Config{
Every: []string{"invalid-day"},
},
expectedError: errInvalidDayName,
},
{
name: "invalid-start-format",
cfg: &Config{
Start: "0000",
},
expectedError: errInvalidMaintenanceStartFormat,
},
{
name: "invalid-start-hours",
cfg: &Config{
Start: "25:00",
},
expectedError: errInvalidMaintenanceStartFormat,
},
{
name: "invalid-start-minutes",
cfg: &Config{
Start: "0:61",
},
expectedError: errInvalidMaintenanceStartFormat,
},
{
name: "invalid-start-minutes-non-numerical",
cfg: &Config{
Start: "00:zz",
},
expectedError: strconv.ErrSyntax,
},
{
name: "invalid-start-hours-non-numerical",
cfg: &Config{
Start: "zz:00",
},
expectedError: strconv.ErrSyntax,
},
{
name: "invalid-duration",
cfg: &Config{
Start: "23:00",
Duration: 0,
},
expectedError: errInvalidMaintenanceDuration,
},
{
name: "every-day-at-2300",
cfg: &Config{
Start: "23:00",
Duration: time.Hour,
},
expectedError: nil,
},
{
name: "every-monday-at-0000",
cfg: &Config{
Start: "00:00",
Duration: 30 * time.Minute,
Every: []string{"Monday"},
},
expectedError: nil,
},
{
name: "every-friday-and-sunday-at-0000-explicitly-enabled",
cfg: &Config{
Enabled: &yes,
Start: "08:00",
Duration: 8 * time.Hour,
Every: []string{"Friday", "Sunday"},
},
expectedError: nil,
},
}
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
err := scenario.cfg.ValidateAndSetDefaults()
if !errors.Is(err, scenario.expectedError) {
t.Errorf("expected %v, got %v", scenario.expectedError, err)
}
})
}
}
func TestConfig_IsUnderMaintenance(t *testing.T) {
yes, no := true, false
now := time.Now().UTC()
scenarios := []struct {
name string
cfg *Config
expected bool
}{
{
name: "disabled",
cfg: &Config{
Enabled: &no,
},
expected: false,
},
{
name: "under-maintenance-explicitly-enabled",
cfg: &Config{
Enabled: &yes,
Start: fmt.Sprintf("%02d:00", now.Hour()),
Duration: 2 * time.Hour,
},
expected: true,
},
{
name: "under-maintenance-starting-now-for-2h",
cfg: &Config{
Start: fmt.Sprintf("%02d:00", now.Hour()),
Duration: 2 * time.Hour,
},
expected: true,
},
{
name: "under-maintenance-starting-now-for-8h",
cfg: &Config{
Start: fmt.Sprintf("%02d:00", now.Hour()),
Duration: 8 * time.Hour,
},
expected: true,
},
{
name: "under-maintenance-starting-4h-ago-for-8h",
cfg: &Config{
Start: fmt.Sprintf("%02d:00", normalizeHour(now.Hour()-4)),
Duration: 8 * time.Hour,
},
expected: true,
},
{
name: "under-maintenance-starting-4h-ago-for-3h",
cfg: &Config{
Start: fmt.Sprintf("%02d:00", normalizeHour(now.Hour()-4)),
Duration: 3 * time.Hour,
},
expected: false,
},
{
name: "under-maintenance-starting-5h-ago-for-1h",
cfg: &Config{
Start: fmt.Sprintf("%02d:00", normalizeHour(now.Hour()-5)),
Duration: time.Hour,
},
expected: false,
},
{
name: "not-under-maintenance-today",
cfg: &Config{
Start: fmt.Sprintf("%02d:00", now.Hour()),
Duration: time.Hour,
Every: []string{now.Add(48 * time.Hour).Weekday().String()},
},
expected: false,
},
}
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
t.Log(scenario.cfg.Start)
t.Log(now)
if err := scenario.cfg.ValidateAndSetDefaults(); err != nil {
t.Fatal("validation shouldn't have returned an error, got", err)
}
isUnderMaintenance := scenario.cfg.IsUnderMaintenance()
if isUnderMaintenance != scenario.expected {
t.Errorf("expected %v, got %v", scenario.expected, isUnderMaintenance)
t.Logf("start=%v; duration=%v; now=%v", scenario.cfg.Start, scenario.cfg.Duration, time.Now().UTC())
}
})
}
}
func normalizeHour(hour int) int {
if hour < 0 {
return hour + 24
}
return hour
}

View File

@@ -1,41 +0,0 @@
package config
import (
"bytes"
"html/template"
)
const (
defaultTitle = "Health Dashboard | Gatus"
defaultLogo = ""
)
// UIConfig is the configuration for the UI of Gatus
type UIConfig struct {
Title string `yaml:"title"` // Title of the page
Logo string `yaml:"logo"` // Logo to display on the page
}
// GetDefaultUIConfig returns a UIConfig struct with the default values
func GetDefaultUIConfig() *UIConfig {
return &UIConfig{
Title: defaultTitle,
Logo: defaultLogo,
}
}
func (cfg *UIConfig) validateAndSetDefaults() error {
if len(cfg.Title) == 0 {
cfg.Title = defaultTitle
}
t, err := template.ParseFiles(StaticFolder + "/index.html")
if err != nil {
return err
}
var buffer bytes.Buffer
err = t.Execute(&buffer, cfg)
if err != nil {
return err
}
return nil
}

48
config/ui/ui.go Normal file
View File

@@ -0,0 +1,48 @@
package ui
import (
"bytes"
"html/template"
)
const (
defaultTitle = "Health Dashboard | Gatus"
defaultLogo = ""
)
var (
// StaticFolder is the path to the location of the static folder from the root path of the project
// The only reason this is exposed is to allow running tests from a different path than the root path of the project
StaticFolder = "./web/static"
)
// Config is the configuration for the UI of Gatus
type Config struct {
Title string `yaml:"title"` // Title of the page
Logo string `yaml:"logo"` // Logo to display on the page
}
// GetDefaultConfig returns a Config struct with the default values
func GetDefaultConfig() *Config {
return &Config{
Title: defaultTitle,
Logo: defaultLogo,
}
}
// ValidateAndSetDefaults validates the UI configuration and sets the default values if necessary.
func (cfg *Config) ValidateAndSetDefaults() error {
if len(cfg.Title) == 0 {
cfg.Title = defaultTitle
}
t, err := template.ParseFiles(StaticFolder + "/index.html")
if err != nil {
return err
}
var buffer bytes.Buffer
err = t.Execute(&buffer, cfg)
if err != nil {
return err
}
return nil
}

26
config/ui/ui_test.go Normal file
View File

@@ -0,0 +1,26 @@
package ui
import (
"testing"
)
func TestConfig_ValidateAndSetDefaults(t *testing.T) {
StaticFolder = "../../web/static"
defer func() {
StaticFolder = "./web/static"
}()
cfg := &Config{Title: ""}
if err := cfg.ValidateAndSetDefaults(); err != nil {
t.Error("expected no error, got", err.Error())
}
}
func TestGetDefaultConfig(t *testing.T) {
defaultConfig := GetDefaultConfig()
if defaultConfig.Title != defaultTitle {
t.Error("expected GetDefaultConfig() to return defaultTitle, got", defaultConfig.Title)
}
if defaultConfig.Logo != defaultLogo {
t.Error("expected GetDefaultConfig() to return defaultLogo, got", defaultConfig.Logo)
}
}

View File

@@ -1,24 +0,0 @@
package config
import "testing"
func TestUIConfig_validateAndSetDefaults(t *testing.T) {
StaticFolder = "../web/static"
defer func() {
StaticFolder = "./web/static"
}()
uiConfig := &UIConfig{Title: ""}
if err := uiConfig.validateAndSetDefaults(); err != nil {
t.Error("expected no error, got", err.Error())
}
}
func TestGetDefaultUIConfig(t *testing.T) {
defaultUIConfig := GetDefaultUIConfig()
if defaultUIConfig.Title != defaultTitle {
t.Error("expected GetDefaultUIConfig() to return defaultTitle, got", defaultUIConfig.Title)
}
if defaultUIConfig.Logo != defaultLogo {
t.Error("expected GetDefaultUIConfig() to return defaultLogo, got", defaultUIConfig.Logo)
}
}

View File

@@ -1,13 +1,21 @@
package config
package web
import (
"fmt"
"math"
)
// WebConfig is the structure which supports the configuration of the endpoint
const (
// DefaultAddress is the default address the service will bind to
DefaultAddress = "0.0.0.0"
// DefaultPort is the default port the service will listen on
DefaultPort = 8080
)
// Config is the structure which supports the configuration of the endpoint
// which provides access to the web frontend
type WebConfig struct {
type Config struct {
// Address to listen on (defaults to 0.0.0.0 specified by DefaultAddress)
Address string `yaml:"address"`
@@ -15,13 +23,13 @@ type WebConfig struct {
Port int `yaml:"port"`
}
// GetDefaultWebConfig returns a WebConfig struct with the default values
func GetDefaultWebConfig() *WebConfig {
return &WebConfig{Address: DefaultAddress, Port: DefaultPort}
// GetDefaultConfig returns a Config struct with the default values
func GetDefaultConfig() *Config {
return &Config{Address: DefaultAddress, Port: DefaultPort}
}
// validateAndSetDefaults checks and sets the default values for fields that are not set
func (web *WebConfig) validateAndSetDefaults() error {
// ValidateAndSetDefaults validates the web configuration and sets the default values if necessary.
func (web *Config) ValidateAndSetDefaults() error {
// Validate the Address
if len(web.Address) == 0 {
web.Address = DefaultAddress
@@ -36,6 +44,6 @@ func (web *WebConfig) validateAndSetDefaults() error {
}
// SocketAddress returns the combination of the Address and the Port
func (web *WebConfig) SocketAddress() string {
func (web *Config) SocketAddress() string {
return fmt.Sprintf("%s:%d", web.Address, web.Port)
}

65
config/web/web_test.go Normal file
View File

@@ -0,0 +1,65 @@
package web
import (
"testing"
)
func TestGetDefaultConfig(t *testing.T) {
defaultConfig := GetDefaultConfig()
if defaultConfig.Port != DefaultPort {
t.Error("expected default config to have the default port")
}
if defaultConfig.Address != DefaultAddress {
t.Error("expected default config to have the default address")
}
}
func TestConfig_ValidateAndSetDefaults(t *testing.T) {
scenarios := []struct {
name string
cfg *Config
expectedAddress string
expectedPort int
expectedErr bool
}{
{
name: "no-explicit-config",
cfg: &Config{},
expectedAddress: "0.0.0.0",
expectedPort: 8080,
expectedErr: false,
},
{
name: "invalid-port",
cfg: &Config{Port: 100000000},
expectedErr: true,
},
}
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
err := scenario.cfg.ValidateAndSetDefaults()
if (err != nil) != scenario.expectedErr {
t.Errorf("expected the existence of an error to be %v, got %v", scenario.expectedErr, err)
return
}
if !scenario.expectedErr {
if scenario.cfg.Port != scenario.expectedPort {
t.Errorf("expected port to be %d, got %d", scenario.expectedPort, scenario.cfg.Port)
}
if scenario.cfg.Address != scenario.expectedAddress {
t.Errorf("expected address to be %s, got %s", scenario.expectedAddress, scenario.cfg.Address)
}
}
})
}
}
func TestConfig_SocketAddress(t *testing.T) {
web := &Config{
Address: "0.0.0.0",
Port: 8081,
}
if web.SocketAddress() != "0.0.0.0:8081" {
t.Errorf("expected %s, got %s", "0.0.0.0:8081", web.SocketAddress())
}
}

View File

@@ -1,15 +0,0 @@
package config
import (
"testing"
)
func TestWebConfig_SocketAddress(t *testing.T) {
web := &WebConfig{
Address: "0.0.0.0",
Port: 8081,
}
if web.SocketAddress() != "0.0.0.0:8081" {
t.Errorf("expected %s, got %s", "0.0.0.0:8081", web.SocketAddress())
}
}

View File

@@ -8,9 +8,10 @@ import (
"os"
"time"
"github.com/TwinProduction/gatus/config"
"github.com/TwinProduction/gatus/controller/handler"
"github.com/TwinProduction/gatus/security"
"github.com/TwinProduction/gatus/v3/config/ui"
"github.com/TwinProduction/gatus/v3/config/web"
"github.com/TwinProduction/gatus/v3/controller/handler"
"github.com/TwinProduction/gatus/v3/security"
)
var (
@@ -20,8 +21,8 @@ var (
)
// Handle creates the router and starts the server
func Handle(securityConfig *security.Config, webConfig *config.WebConfig, uiConfig *config.UIConfig, enableMetrics bool) {
var router http.Handler = handler.CreateRouter(config.StaticFolder, securityConfig, uiConfig, enableMetrics)
func Handle(securityConfig *security.Config, webConfig *web.Config, uiConfig *ui.Config, enableMetrics bool) {
var router http.Handler = handler.CreateRouter(ui.StaticFolder, securityConfig, uiConfig, enableMetrics)
if os.Getenv("ENVIRONMENT") == "dev" {
router = handler.DevelopmentCORS(router)
}

View File

@@ -7,13 +7,14 @@ import (
"os"
"testing"
"github.com/TwinProduction/gatus/config"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/config"
"github.com/TwinProduction/gatus/v3/config/web"
"github.com/TwinProduction/gatus/v3/core"
)
func TestHandle(t *testing.T) {
cfg := &config.Config{
Web: &config.WebConfig{
Web: &web.Config{
Address: "0.0.0.0",
Port: rand.Intn(65534),
},

View File

@@ -7,8 +7,8 @@ import (
"strings"
"time"
"github.com/TwinProduction/gatus/storage"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/v3/storage"
"github.com/TwinProduction/gatus/v3/storage/store/common"
"github.com/gorilla/mux"
)

View File

@@ -7,10 +7,10 @@ import (
"testing"
"time"
"github.com/TwinProduction/gatus/config"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage"
"github.com/TwinProduction/gatus/watchdog"
"github.com/TwinProduction/gatus/v3/config"
"github.com/TwinProduction/gatus/v3/core"
"github.com/TwinProduction/gatus/v3/storage"
"github.com/TwinProduction/gatus/v3/watchdog"
)
func TestUptimeBadge(t *testing.T) {

View File

@@ -7,8 +7,8 @@ import (
"sort"
"time"
"github.com/TwinProduction/gatus/storage"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/v3/storage"
"github.com/TwinProduction/gatus/v3/storage/store/common"
"github.com/gorilla/mux"
"github.com/wcharczuk/go-chart/v2"
"github.com/wcharczuk/go-chart/v2/drawing"

View File

@@ -6,10 +6,10 @@ import (
"testing"
"time"
"github.com/TwinProduction/gatus/config"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage"
"github.com/TwinProduction/gatus/watchdog"
"github.com/TwinProduction/gatus/v3/config"
"github.com/TwinProduction/gatus/v3/core"
"github.com/TwinProduction/gatus/v3/storage"
"github.com/TwinProduction/gatus/v3/watchdog"
)
func TestResponseTimeChart(t *testing.T) {

View File

@@ -3,14 +3,14 @@ package handler
import (
"net/http"
"github.com/TwinProduction/gatus/config"
"github.com/TwinProduction/gatus/security"
"github.com/TwinProduction/gatus/v3/config/ui"
"github.com/TwinProduction/gatus/v3/security"
"github.com/TwinProduction/health"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func CreateRouter(staticFolder string, securityConfig *security.Config, uiConfig *config.UIConfig, enabledMetrics bool) *mux.Router {
func CreateRouter(staticFolder string, securityConfig *security.Config, uiConfig *ui.Config, enabledMetrics bool) *mux.Router {
router := mux.NewRouter()
if enabledMetrics {
router.Handle("/metrics", promhttp.Handler()).Methods("GET")

View File

@@ -10,9 +10,9 @@ import (
"strings"
"time"
"github.com/TwinProduction/gatus/storage"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/v3/storage"
"github.com/TwinProduction/gatus/v3/storage/store/common"
"github.com/TwinProduction/gatus/v3/storage/store/common/paging"
"github.com/TwinProduction/gocache"
"github.com/gorilla/mux"
)

View File

@@ -6,10 +6,10 @@ import (
"testing"
"time"
"github.com/TwinProduction/gatus/config"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage"
"github.com/TwinProduction/gatus/watchdog"
"github.com/TwinProduction/gatus/v3/config"
"github.com/TwinProduction/gatus/v3/core"
"github.com/TwinProduction/gatus/v3/storage"
"github.com/TwinProduction/gatus/v3/watchdog"
)
var (

View File

@@ -5,10 +5,10 @@ import (
"log"
"net/http"
"github.com/TwinProduction/gatus/config"
"github.com/TwinProduction/gatus/v3/config/ui"
)
func SinglePageApplication(staticFolder string, ui *config.UIConfig) http.HandlerFunc {
func SinglePageApplication(staticFolder string, ui *ui.Config) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
t, err := template.ParseFiles(staticFolder + "/index.html")
if err != nil {

View File

@@ -6,10 +6,10 @@ import (
"testing"
"time"
"github.com/TwinProduction/gatus/config"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage"
"github.com/TwinProduction/gatus/watchdog"
"github.com/TwinProduction/gatus/v3/config"
"github.com/TwinProduction/gatus/v3/core"
"github.com/TwinProduction/gatus/v3/storage"
"github.com/TwinProduction/gatus/v3/watchdog"
)
func TestSinglePageApplication(t *testing.T) {

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"strconv"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/v3/storage/store/common"
)
const (

View File

@@ -6,8 +6,8 @@ import (
"strings"
"time"
"github.com/TwinProduction/gatus/jsonpath"
"github.com/TwinProduction/gatus/pattern"
"github.com/TwinProduction/gatus/v3/jsonpath"
"github.com/TwinProduction/gatus/v3/pattern"
)
const (

View File

@@ -7,15 +7,14 @@ import (
)
func TestCondition_evaluate(t *testing.T) {
type scenario struct {
scenarios := []struct {
Name string
Condition Condition
Result *Result
DontResolveFailedConditions bool
ExpectedSuccess bool
ExpectedOutput string
}
scenarios := []scenario{
}{
{
Name: "ip",
Condition: Condition("[IP] == 127.0.0.1"),

View File

@@ -3,7 +3,7 @@ package core
import (
"testing"
"github.com/TwinProduction/gatus/pattern"
"github.com/TwinProduction/gatus/v3/pattern"
)
func TestIntegrationQuery(t *testing.T) {

View File

@@ -12,10 +12,10 @@ import (
"strings"
"time"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/client"
"github.com/TwinProduction/gatus/core/ui"
"github.com/TwinProduction/gatus/util"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/client"
"github.com/TwinProduction/gatus/v3/core/ui"
"github.com/TwinProduction/gatus/v3/util"
)
const (
@@ -44,7 +44,11 @@ var (
)
// Service is the configuration of a monitored endpoint
// XXX: Rename this to Endpoint in v4.0.0?
type Service struct {
// Enabled defines whether to enable the service
Enabled *bool `yaml:"enabled,omitempty"`
// Name of the service. Can be anything.
Name string `yaml:"name"`
@@ -91,6 +95,14 @@ type Service struct {
NumberOfSuccessesInARow int
}
// IsEnabled returns whether the service is enabled or not
func (service Service) IsEnabled() bool {
if service.Enabled == nil {
return true
}
return *service.Enabled
}
// ValidateAndSetDefaults validates the service's configuration and sets the default value of fields that have one
func (service *Service) ValidateAndSetDefaults() error {
// Set default values
@@ -206,7 +218,8 @@ func (service *Service) call(result *Result) {
isServiceTCP := strings.HasPrefix(service.URL, "tcp://")
isServiceICMP := strings.HasPrefix(service.URL, "icmp://")
isServiceStartTLS := strings.HasPrefix(service.URL, "starttls://")
isServiceHTTP := !isServiceDNS && !isServiceTCP && !isServiceICMP && !isServiceStartTLS
isServiceTLS := strings.HasPrefix(service.URL, "tls://")
isServiceHTTP := !isServiceDNS && !isServiceTCP && !isServiceICMP && !isServiceStartTLS && !isServiceTLS
if isServiceHTTP {
request = service.buildHTTPRequest()
}
@@ -214,8 +227,12 @@ func (service *Service) call(result *Result) {
if isServiceDNS {
service.DNS.query(service.URL, result)
result.Duration = time.Since(startTime)
} else if isServiceStartTLS {
} else if isServiceStartTLS || isServiceTLS {
if isServiceStartTLS {
result.Connected, certificate, err = client.CanPerformStartTLS(strings.TrimPrefix(service.URL, "starttls://"), service.ClientConfig)
} else {
result.Connected, certificate, err = client.CanPerformStartTLS(strings.TrimPrefix(service.URL, "tls://"), service.ClientConfig)
}
if err != nil {
result.AddError(err.Error())
return

View File

@@ -6,15 +6,27 @@ import (
"testing"
"time"
"github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/client"
"github.com/TwinProduction/gatus/v3/alerting/alert"
"github.com/TwinProduction/gatus/v3/client"
)
func TestService_IsEnabled(t *testing.T) {
if !(Service{Enabled: nil}).IsEnabled() {
t.Error("service.IsEnabled() should've returned true, because Enabled was set to nil")
}
if value := false; (Service{Enabled: &value}).IsEnabled() {
t.Error("service.IsEnabled() should've returned false, because Enabled was set to false")
}
if value := true; !(Service{Enabled: &value}).IsEnabled() {
t.Error("Service.IsEnabled() should've returned true, because Enabled was set to true")
}
}
func TestService_ValidateAndSetDefaults(t *testing.T) {
condition := Condition("[STATUS] == 200")
service := Service{
Name: "twinnation-health",
URL: "https://twinnation.org/health",
Name: "website-health",
URL: "https://twin.sh/health",
Conditions: []*Condition{&condition},
Alerts: []*alert.Alert{{Type: alert.TypePagerDuty}},
}
@@ -58,8 +70,8 @@ func TestService_ValidateAndSetDefaults(t *testing.T) {
func TestService_ValidateAndSetDefaultsWithClientConfig(t *testing.T) {
condition := Condition("[STATUS] == 200")
service := Service{
Name: "twinnation-health",
URL: "https://twinnation.org/health",
Name: "website-health",
URL: "https://twin.sh/health",
Conditions: []*Condition{&condition},
ClientConfig: &client.Config{
Insecure: true,
@@ -147,8 +159,8 @@ func TestService_ValidateAndSetDefaultsWithDNS(t *testing.T) {
func TestService_buildHTTPRequest(t *testing.T) {
condition := Condition("[STATUS] == 200")
service := Service{
Name: "twinnation-health",
URL: "https://twinnation.org/health",
Name: "website-health",
URL: "https://twin.sh/health",
Conditions: []*Condition{&condition},
}
service.ValidateAndSetDefaults()
@@ -156,8 +168,8 @@ func TestService_buildHTTPRequest(t *testing.T) {
if request.Method != "GET" {
t.Error("request.Method should've been GET, but was", request.Method)
}
if request.Host != "twinnation.org" {
t.Error("request.Host should've been twinnation.org, but was", request.Host)
if request.Host != "twin.sh" {
t.Error("request.Host should've been twin.sh, but was", request.Host)
}
if userAgent := request.Header.Get("User-Agent"); userAgent != GatusUserAgent {
t.Errorf("request.Header.Get(User-Agent) should've been %s, but was %s", GatusUserAgent, userAgent)
@@ -167,8 +179,8 @@ func TestService_buildHTTPRequest(t *testing.T) {
func TestService_buildHTTPRequestWithCustomUserAgent(t *testing.T) {
condition := Condition("[STATUS] == 200")
service := Service{
Name: "twinnation-health",
URL: "https://twinnation.org/health",
Name: "website-health",
URL: "https://twin.sh/health",
Conditions: []*Condition{&condition},
Headers: map[string]string{
"User-Agent": "Test/2.0",
@@ -179,8 +191,8 @@ func TestService_buildHTTPRequestWithCustomUserAgent(t *testing.T) {
if request.Method != "GET" {
t.Error("request.Method should've been GET, but was", request.Method)
}
if request.Host != "twinnation.org" {
t.Error("request.Host should've been twinnation.org, but was", request.Host)
if request.Host != "twin.sh" {
t.Error("request.Host should've been twin.sh, but was", request.Host)
}
if userAgent := request.Header.Get("User-Agent"); userAgent != "Test/2.0" {
t.Errorf("request.Header.Get(User-Agent) should've been %s, but was %s", "Test/2.0", userAgent)
@@ -190,8 +202,8 @@ func TestService_buildHTTPRequestWithCustomUserAgent(t *testing.T) {
func TestService_buildHTTPRequestWithHostHeader(t *testing.T) {
condition := Condition("[STATUS] == 200")
service := Service{
Name: "twinnation-health",
URL: "https://twinnation.org/health",
Name: "website-health",
URL: "https://twin.sh/health",
Method: "POST",
Conditions: []*Condition{&condition},
Headers: map[string]string{
@@ -211,8 +223,8 @@ func TestService_buildHTTPRequestWithHostHeader(t *testing.T) {
func TestService_buildHTTPRequestWithGraphQLEnabled(t *testing.T) {
condition := Condition("[STATUS] == 200")
service := Service{
Name: "twinnation-graphql",
URL: "https://twinnation.org/graphql",
Name: "website-graphql",
URL: "https://twin.sh/graphql",
Method: "POST",
Conditions: []*Condition{&condition},
GraphQL: true,
@@ -243,8 +255,8 @@ func TestIntegrationEvaluateHealth(t *testing.T) {
condition := Condition("[STATUS] == 200")
bodyCondition := Condition("[BODY].status == UP")
service := Service{
Name: "twinnation-health",
URL: "https://twinnation.org/health",
Name: "website-health",
URL: "https://twin.sh/health",
Conditions: []*Condition{&condition, &bodyCondition},
}
service.ValidateAndSetDefaults()
@@ -263,8 +275,8 @@ func TestIntegrationEvaluateHealth(t *testing.T) {
func TestIntegrationEvaluateHealthWithFailure(t *testing.T) {
condition := Condition("[STATUS] == 500")
service := Service{
Name: "twinnation-health",
URL: "https://twinnation.org/health",
Name: "website-health",
URL: "https://twin.sh/health",
Conditions: []*Condition{&condition},
}
service.ValidateAndSetDefaults()

View File

@@ -39,9 +39,9 @@ alerting:
You can now add alerts of type `pagerduty` in the services you've defined, like so:
```yaml
services:
- name: twinnation
- name: website
interval: 30s
url: "https://twinnation.org/health"
url: "https://twin.sh/health"
alerts:
- type: pagerduty
enabled: true
@@ -56,7 +56,7 @@ services:
```
The sample above will do the following:
- Send a request to the `https://twinnation.org/health` (`services[].url`) specified every **30s** (`services[].interval`)
- Send a request to the `https://twin.sh/health` (`services[].url`) specified every **30s** (`services[].interval`)
- Evaluate the conditions to determine whether the service is "healthy" or not
- **If all conditions are not met 3 (`services[].alerts[].failure-threshold`) times in a row**: Gatus will create a new incident
- **If, after an incident has been triggered, all conditions are met 5 (`services[].alerts[].success-threshold`) times in a row _AND_ `services[].alerts[].send-on-resolved` is set to `true`**: Gatus will resolve the triggered incident

View File

@@ -1,16 +1,16 @@
metrics: true
services:
- name: TwiNNatioN
url: https://twinnation.org/health
- name: website
url: https://twin.sh/health
interval: 30s
conditions:
- "[STATUS] == 200"
- name: GitHub
- name: github
url: https://api.github.com/healthz
interval: 5m
conditions:
- "[STATUS] == 200"
- name: Example
- name: example
url: https://example.com/
conditions:
- "[STATUS] == 200"

View File

@@ -3,12 +3,12 @@ data:
config.yaml: |
metrics: true
services:
- name: TwiNNatioN
url: https://twinnation.org/health
- name: website
url: https://twin.sh/health
interval: 1m
conditions:
- "[STATUS] == 200"
- name: GitHub
- name: github
url: https://api.github.com/healthz
interval: 5m
conditions:
@@ -23,7 +23,7 @@ data:
- "[BODY].text == pat(*cat*)"
- "[STATUS] == pat(2*)"
- "[CONNECTED] == true"
- name: Example
- name: example
url: https://example.com/
conditions:
- "[STATUS] == 200"

27
go.mod
View File

@@ -1,23 +1,46 @@
module github.com/TwinProduction/gatus
module github.com/TwinProduction/gatus/v3
go 1.16
go 1.17
require (
github.com/TwinProduction/gocache v1.2.3
github.com/TwinProduction/health v1.0.0
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/protobuf v1.4.3 // indirect
github.com/google/go-cmp v0.5.4 // indirect
github.com/gorilla/mux v1.8.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/lib/pq v1.10.3
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/miekg/dns v1.1.35
github.com/prometheus/client_golang v1.9.0
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.15.0 // indirect
github.com/prometheus/procfs v0.2.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/wcharczuk/go-chart/v2 v2.1.0
go.etcd.io/bbolt v1.3.5 // indirect
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect
golang.org/x/mod v0.4.0 // indirect
golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/yaml.v2 v2.4.0
lukechampine.com/uint128 v1.1.1 // indirect
modernc.org/cc/v3 v3.33.6 // indirect
modernc.org/ccgo/v3 v3.9.5 // indirect
modernc.org/libc v1.9.11 // indirect
modernc.org/mathutil v1.4.0 // indirect
modernc.org/memory v1.0.4 // indirect
modernc.org/opt v0.1.1 // indirect
modernc.org/sqlite v1.11.2
modernc.org/strutil v1.1.1 // indirect
modernc.org/token v1.0.0 // indirect
)

View File

@@ -7,10 +7,10 @@ import (
"syscall"
"time"
"github.com/TwinProduction/gatus/config"
"github.com/TwinProduction/gatus/controller"
"github.com/TwinProduction/gatus/storage"
"github.com/TwinProduction/gatus/watchdog"
"github.com/TwinProduction/gatus/v3/config"
"github.com/TwinProduction/gatus/v3/controller"
"github.com/TwinProduction/gatus/v3/storage"
"github.com/TwinProduction/gatus/v3/watchdog"
)
func main() {

View File

@@ -5,7 +5,7 @@ import (
"strconv"
"sync"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/core"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)

View File

@@ -5,9 +5,9 @@ import (
"log"
"time"
"github.com/TwinProduction/gatus/storage/store"
"github.com/TwinProduction/gatus/storage/store/memory"
"github.com/TwinProduction/gatus/storage/store/sql"
"github.com/TwinProduction/gatus/v3/storage/store"
"github.com/TwinProduction/gatus/v3/storage/store/memory"
"github.com/TwinProduction/gatus/v3/storage/store/sql"
)
var (

View File

@@ -4,7 +4,7 @@ import (
"testing"
"time"
"github.com/TwinProduction/gatus/storage/store/sql"
"github.com/TwinProduction/gatus/v3/storage/store/sql"
)
func TestGet(t *testing.T) {

View File

@@ -6,10 +6,10 @@ import (
"sync"
"time"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/util"
"github.com/TwinProduction/gatus/v3/core"
"github.com/TwinProduction/gatus/v3/storage/store/common"
"github.com/TwinProduction/gatus/v3/storage/store/common/paging"
"github.com/TwinProduction/gatus/v3/util"
"github.com/TwinProduction/gocache"
)

View File

@@ -4,8 +4,8 @@ import (
"testing"
"time"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/v3/core"
"github.com/TwinProduction/gatus/v3/storage/store/common/paging"
)
var (

View File

@@ -3,7 +3,7 @@ package memory
import (
"time"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/core"
)
const (

View File

@@ -4,7 +4,7 @@ import (
"testing"
"time"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/core"
)
func BenchmarkProcessUptimeAfterResult(b *testing.B) {

View File

@@ -4,7 +4,7 @@ import (
"testing"
"time"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/v3/core"
)
func TestProcessUptimeAfterResult(t *testing.T) {

View File

@@ -1,9 +1,9 @@
package memory
import (
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/v3/core"
"github.com/TwinProduction/gatus/v3/storage/store/common"
"github.com/TwinProduction/gatus/v3/storage/store/common/paging"
)
// ShallowCopyServiceStatus returns a shallow copy of a ServiceStatus with only the results

View File

@@ -3,9 +3,9 @@ package memory
import (
"testing"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/v3/core"
"github.com/TwinProduction/gatus/v3/storage/store/common"
"github.com/TwinProduction/gatus/v3/storage/store/common/paging"
)
func BenchmarkShallowCopyServiceStatus(b *testing.B) {

View File

@@ -4,9 +4,9 @@ import (
"testing"
"time"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/v3/core"
"github.com/TwinProduction/gatus/v3/storage/store/common"
"github.com/TwinProduction/gatus/v3/storage/store/common/paging"
)
func TestAddResult(t *testing.T) {

View File

@@ -8,10 +8,10 @@ import (
"strings"
"time"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/util"
"github.com/TwinProduction/gatus/v3/core"
"github.com/TwinProduction/gatus/v3/storage/store/common"
"github.com/TwinProduction/gatus/v3/storage/store/common/paging"
"github.com/TwinProduction/gatus/v3/util"
_ "github.com/lib/pq"
_ "modernc.org/sqlite"
)
@@ -461,11 +461,8 @@ func (s *Store) updateServiceUptime(tx *sql.Tx, serviceID int64, result *core.Re
successfulExecutions,
result.Duration.Milliseconds(),
)
if err != nil {
return err
}
return nil
}
func (s *Store) getAllServiceKeys(tx *sql.Tx) (keys []string, err error) {
rows, err := tx.Query("SELECT service_key FROM service ORDER BY service_key")
@@ -477,7 +474,6 @@ func (s *Store) getAllServiceKeys(tx *sql.Tx) (keys []string, err error) {
_ = rows.Scan(&key)
keys = append(keys, key)
}
_ = rows.Close()
return
}
@@ -546,7 +542,6 @@ func (s *Store) getEventsByServiceID(tx *sql.Tx, serviceID int64, page, pageSize
_ = rows.Scan(&event.Type, &event.Timestamp)
events = append(events, event)
}
_ = rows.Close()
return
}
@@ -579,7 +574,6 @@ func (s *Store) getResultsByServiceID(tx *sql.Tx, serviceID int64, page, pageSiz
results = append([]*core.Result{result}, results...)
idResultMap[id] = result
}
_ = rows.Close()
// Get condition results
args := make([]interface{}, 0, len(idResultMap))
query := `SELECT service_result_id, condition, success
@@ -596,6 +590,7 @@ func (s *Store) getResultsByServiceID(tx *sql.Tx, serviceID int64, page, pageSiz
if err != nil {
return nil, err
}
defer rows.Close() // explicitly defer the close in case an error happens during the scan
for rows.Next() {
conditionResult := &core.ConditionResult{}
var serviceResultID int64
@@ -626,9 +621,7 @@ func (s *Store) getServiceUptime(tx *sql.Tx, serviceID int64, from, to time.Time
var totalExecutions, totalSuccessfulExecutions, totalResponseTime int
for rows.Next() {
_ = rows.Scan(&totalExecutions, &totalSuccessfulExecutions, &totalResponseTime)
break
}
_ = rows.Close()
if totalExecutions > 0 {
uptime = float64(totalSuccessfulExecutions) / float64(totalExecutions)
avgResponseTime = time.Duration(float64(totalResponseTime)/float64(totalExecutions)) * time.Millisecond
@@ -657,7 +650,6 @@ func (s *Store) getServiceAverageResponseTime(tx *sql.Tx, serviceID int64, from,
for rows.Next() {
_ = rows.Scan(&totalExecutions, &totalResponseTime)
}
_ = rows.Close()
if totalExecutions == 0 {
return 0, nil
}
@@ -688,7 +680,6 @@ func (s *Store) getServiceHourlyAverageResponseTimes(tx *sql.Tx, serviceID int64
_ = rows.Scan(&unixTimestampFlooredAtHour, &totalExecutions, &totalResponseTime)
hourlyAverageResponseTimes[unixTimestampFlooredAtHour] = int(float64(totalResponseTime) / float64(totalExecutions))
}
_ = rows.Close()
return hourlyAverageResponseTimes, nil
}
@@ -735,9 +726,7 @@ func (s *Store) getAgeOfOldestServiceUptimeEntry(tx *sql.Tx, serviceID int64) (t
for rows.Next() {
_ = rows.Scan(&oldestServiceUptimeUnixTimestamp)
found = true
break
}
_ = rows.Close()
if !found {
return 0, errNoRowsReturned
}

View File

@@ -4,9 +4,9 @@ import (
"testing"
"time"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/v3/core"
"github.com/TwinProduction/gatus/v3/storage/store/common"
"github.com/TwinProduction/gatus/v3/storage/store/common/paging"
)
var (

View File

@@ -3,10 +3,10 @@ package store
import (
"time"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/storage/store/memory"
"github.com/TwinProduction/gatus/storage/store/sql"
"github.com/TwinProduction/gatus/v3/core"
"github.com/TwinProduction/gatus/v3/storage/store/common/paging"
"github.com/TwinProduction/gatus/v3/storage/store/memory"
"github.com/TwinProduction/gatus/v3/storage/store/sql"
)
// Store is the interface that each stores should implement

View File

@@ -4,10 +4,10 @@ import (
"testing"
"time"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/storage/store/memory"
"github.com/TwinProduction/gatus/storage/store/sql"
"github.com/TwinProduction/gatus/v3/core"
"github.com/TwinProduction/gatus/v3/storage/store/common/paging"
"github.com/TwinProduction/gatus/v3/storage/store/memory"
"github.com/TwinProduction/gatus/v3/storage/store/sql"
)
func BenchmarkStore_GetAllServiceStatuses(b *testing.B) {

View File

@@ -4,11 +4,11 @@ import (
"testing"
"time"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/storage/store/memory"
"github.com/TwinProduction/gatus/storage/store/sql"
"github.com/TwinProduction/gatus/v3/core"
"github.com/TwinProduction/gatus/v3/storage/store/common"
"github.com/TwinProduction/gatus/v3/storage/store/common/paging"
"github.com/TwinProduction/gatus/v3/storage/store/memory"
"github.com/TwinProduction/gatus/v3/storage/store/sql"
)
var (

View File

@@ -1,11 +0,0 @@
module github.com/TwinProduction/gocache
go 1.16
require (
github.com/go-redis/redis v6.15.9+incompatible
github.com/onsi/ginkgo v1.14.1 // indirect
github.com/onsi/gomega v1.10.2 // indirect
github.com/tidwall/redcon v1.3.2
go.etcd.io/bbolt v1.3.5
)

View File

@@ -1,63 +0,0 @@
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4=
github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs=
github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/tidwall/redcon v1.3.2 h1:8INx/Nm3VSUbDUT16TH1rMgYQsbXNqy9xcX70edHXbo=
github.com/tidwall/redcon v1.3.2/go.mod h1:bdYBm4rlcWpst2XMwKVzWDF9CoUxEbUmM7CQrKeOZas=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -1,3 +0,0 @@
module github.com/TwinProduction/health
go 1.15

View File

@@ -1,3 +0,0 @@
module github.com/cespare/xxhash/v2
go 1.11

View File

View File

@@ -1,5 +0,0 @@
module github.com/go-ping/ping
go 1.14
require golang.org/x/net v0.0.0-20200904194848-62affa334b73

View File

@@ -1,10 +0,0 @@
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -1,3 +0,0 @@
module github.com/gorilla/mux
go 1.12

3
vendor/github.com/lib/pq/go.mod generated vendored
View File

@@ -1,3 +0,0 @@
module github.com/lib/pq
go 1.13

View File

@@ -1,5 +0,0 @@
module github.com/mattn/go-isatty
go 1.12
require golang.org/x/sys v0.0.0-20200116001909-b77594299b42

View File

@@ -1,2 +0,0 @@
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

11
vendor/github.com/miekg/dns/go.mod generated vendored
View File

@@ -1,11 +0,0 @@
module github.com/miekg/dns
go 1.12
require (
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
golang.org/x/net v0.0.0-20190923162816-aa69164e4478
golang.org/x/sync v0.0.0-20190423024810-112230192c58
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 // indirect
)

39
vendor/github.com/miekg/dns/go.sum generated vendored
View File

@@ -1,39 +0,0 @@
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4 h1:Vk3wNqEZwyGyei9yq5ekj7frek2u7HUfffJ1/opblzc=
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM=
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3 h1:dgd4x4kJt7G4k4m93AYLzM8Ni6h2qLTfh9n9vXJT3/0=
golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 h1:O33LKL7WyJgjN9CvxfTIomjIClbd/Kq86/iipowHQU0=
golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -1,9 +0,0 @@
module github.com/prometheus/procfs
go 1.12
require (
github.com/google/go-cmp v0.3.1
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e
)

View File

@@ -1,6 +0,0 @@
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e h1:LwyF2AFISC9nVbS6MgzsaQNSUsRXI49GS+YQ5KX/QH0=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@@ -1,3 +0,0 @@
module github.com/remyoudompheng/bigfft
go 1.12

View File

@@ -1,8 +0,0 @@
module github.com/wcharczuk/go-chart/v2
go 1.15
require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5
)

View File

@@ -1,5 +0,0 @@
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

5
vendor/go.etcd.io/bbolt/go.mod generated vendored
View File

@@ -1,5 +0,0 @@
module go.etcd.io/bbolt
go 1.12
require golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5

2
vendor/go.etcd.io/bbolt/go.sum generated vendored
View File

@@ -1,2 +0,0 @@
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

3
vendor/golang.org/x/xerrors/go.mod generated vendored
View File

@@ -1,3 +0,0 @@
module golang.org/x/xerrors
go 1.11

5
vendor/gopkg.in/yaml.v2/go.mod generated vendored
View File

@@ -1,5 +0,0 @@
module gopkg.in/yaml.v2
go 1.15
require gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405

View File

@@ -1,3 +0,0 @@
module lukechampine.com/uint128
go 1.12

Some files were not shown because too many files have changed in this diff Show More