Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3136,6 +3136,11 @@ endpoints:
conditions:
- "[DOMAIN_EXPIRATION] > 720h"
- "[CERTIFICATE_EXPIRATION] > 240h"
- name: check-domain-expiration-only
url: "domain://example.org"
interval: 1h
conditions:
- "[DOMAIN_EXPIRATION] > 720h"
```

> ⚠ The usage of the `[DOMAIN_EXPIRATION]` placeholder requires Gatus to use RDAP, or as a fallback, send a request to the official IANA WHOIS service
Expand Down
38 changes: 25 additions & 13 deletions config/endpoint/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const (
TypeGRPC Type = "GRPC"
TypeWS Type = "WEBSOCKET"
TypeSSH Type = "SSH"
TypeDomain Type = "DOMAIN"
TypeUNKNOWN Type = "UNKNOWN"
)

Expand Down Expand Up @@ -161,29 +162,36 @@ func (e *Endpoint) IsEnabled() bool {

// Type returns the endpoint type
func (e *Endpoint) Type() Type {
switch {
case e.DNSConfig != nil:
if e.DNSConfig != nil {
return TypeDNS
case strings.HasPrefix(e.URL, "tcp://"):
}
before, _, ok := strings.Cut(e.URL, ":")
if !ok {
return TypeUNKNOWN
}
switch before {
case "tcp":
return TypeTCP
case strings.HasPrefix(e.URL, "sctp://"):
case "sctp":
return TypeSCTP
case strings.HasPrefix(e.URL, "udp://"):
case "udp":
return TypeUDP
case strings.HasPrefix(e.URL, "icmp://"):
case "icmp":
return TypeICMP
case strings.HasPrefix(e.URL, "starttls://"):
case "starttls":
return TypeSTARTTLS
case strings.HasPrefix(e.URL, "tls://"):
case "tls":
return TypeTLS
case strings.HasPrefix(e.URL, "http://") || strings.HasPrefix(e.URL, "https://"):
case "http", "https":
return TypeHTTP
case strings.HasPrefix(e.URL, "grpc://") || strings.HasPrefix(e.URL, "grpcs://"):
case "grpc", "grpcs":
return TypeGRPC
case strings.HasPrefix(e.URL, "ws://") || strings.HasPrefix(e.URL, "wss://"):
case "ws", "wss":
return TypeWS
case strings.HasPrefix(e.URL, "ssh://"):
case "ssh":
return TypeSSH
case "domain":
return TypeDomain
default:
return TypeUNKNOWN
}
Expand Down Expand Up @@ -452,8 +460,12 @@ func (e *Endpoint) call(result *Result) {
var err error
var certificate *x509.Certificate
endpointType := e.Type()
if endpointType == TypeHTTP {
switch endpointType {
case TypeHTTP:
request = e.buildHTTPRequest()
case TypeDomain:
// domain expiration checked before call `call`
return
}
startTime := time.Now()
if endpointType == TypeDNS {
Expand Down
237 changes: 140 additions & 97 deletions config/endpoint/endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,27 @@ func TestEndpoint(t *testing.T) {
return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody}
}),
},
{
Name: "domain-expiration-without-http-request",
Endpoint: Endpoint{
Name: "domain-check",
URL: "domain://twin.sh",
Conditions: []Condition{"[DOMAIN_EXPIRATION] > 100h"},
Interval: 5 * time.Minute,
},
ExpectedResult: &Result{
Success: true,
Connected: false,
Hostname: "twin.sh",
ConditionResults: []*ConditionResult{
{Condition: "[DOMAIN_EXPIRATION] > 100h", Success: true},
},
DomainExpiration: 999999 * time.Hour, // Note that this test only checks if it's non-zero.
},
MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response {
return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody}
}),
},
{
Name: "endpoint-that-will-time-out-and-hidden-hostname",
Endpoint: Endpoint{
Expand Down Expand Up @@ -280,104 +301,112 @@ func TestEndpoint_IsEnabled(t *testing.T) {
}
}

type testEndpoint_typeArgs struct {
URL string
DNS *dns.Config
SSH *ssh.Config
}

var testEndpoint_typeData = []struct {
args testEndpoint_typeArgs
want Type
}{
{
args: testEndpoint_typeArgs{
URL: "8.8.8.8",
DNS: &dns.Config{
QueryType: "A",
QueryName: "example.com",
},
},
want: TypeDNS,
},
{
args: testEndpoint_typeArgs{
URL: "tcp://127.0.0.1:6379",
},
want: TypeTCP,
},
{
args: testEndpoint_typeArgs{
URL: "icmp://example.com",
},
want: TypeICMP,
},
{
args: testEndpoint_typeArgs{
URL: "sctp://example.com",
},
want: TypeSCTP,
},
{
args: testEndpoint_typeArgs{
URL: "udp://example.com",
},
want: TypeUDP,
},
{
args: testEndpoint_typeArgs{
URL: "starttls://smtp.gmail.com:587",
},
want: TypeSTARTTLS,
},
{
args: testEndpoint_typeArgs{
URL: "tls://example.com:443",
},
want: TypeTLS,
},
{
args: testEndpoint_typeArgs{
URL: "https://twin.sh/health",
},
want: TypeHTTP,
},
{
args: testEndpoint_typeArgs{
URL: "wss://example.com/",
},
want: TypeWS,
},
{
args: testEndpoint_typeArgs{
URL: "ws://example.com/",
},
want: TypeWS,
},
{
args: testEndpoint_typeArgs{
URL: "ssh://example.com:22",
SSH: &ssh.Config{
Username: "root",
Password: "password",
},
},
want: TypeSSH,
},
{
args: testEndpoint_typeArgs{
URL: "domain://example.org",
},
want: TypeDomain,
},
{
args: testEndpoint_typeArgs{
URL: "invalid://example.org",
},
want: TypeUNKNOWN,
},
{
args: testEndpoint_typeArgs{
URL: "no-scheme",
},
want: TypeUNKNOWN,
},
}

func TestEndpoint_Type(t *testing.T) {
type args struct {
URL string
DNS *dns.Config
SSH *ssh.Config
}
tests := []struct {
args args
want Type
}{
{
args: args{
URL: "8.8.8.8",
DNS: &dns.Config{
QueryType: "A",
QueryName: "example.com",
},
},
want: TypeDNS,
},
{
args: args{
URL: "tcp://127.0.0.1:6379",
},
want: TypeTCP,
},
{
args: args{
URL: "icmp://example.com",
},
want: TypeICMP,
},
{
args: args{
URL: "sctp://example.com",
},
want: TypeSCTP,
},
{
args: args{
URL: "udp://example.com",
},
want: TypeUDP,
},
{
args: args{
URL: "starttls://smtp.gmail.com:587",
},
want: TypeSTARTTLS,
},
{
args: args{
URL: "tls://example.com:443",
},
want: TypeTLS,
},
{
args: args{
URL: "https://twin.sh/health",
},
want: TypeHTTP,
},
{
args: args{
URL: "wss://example.com/",
},
want: TypeWS,
},
{
args: args{
URL: "ws://example.com/",
},
want: TypeWS,
},
{
args: args{
URL: "ssh://example.com:22",
SSH: &ssh.Config{
Username: "root",
Password: "password",
},
},
want: TypeSSH,
},
{
args: args{
URL: "invalid://example.org",
},
want: TypeUNKNOWN,
},
{
args: args{
URL: "no-scheme",
},
want: TypeUNKNOWN,
},
}
for _, tt := range tests {
for _, tt := range testEndpoint_typeData {
t.Run(string(tt.want), func(t *testing.T) {
endpoint := Endpoint{
URL: tt.args.URL,
Expand All @@ -390,6 +419,20 @@ func TestEndpoint_Type(t *testing.T) {
}
}

func BenchmarkEndpoint_Type(b *testing.B) {
for b.Loop() {
for _, tt := range testEndpoint_typeData {
endpoint := Endpoint{
URL: tt.args.URL,
DNSConfig: tt.args.DNS,
}
if got := endpoint.Type(); got != tt.want {
b.Errorf("Endpoint.Type() = %v, want %v", got, tt.want)
}
}
}
}

func TestEndpoint_ValidateAndSetDefaults(t *testing.T) {
endpoint := Endpoint{
Name: "website-health",
Expand Down