Skip to content
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3241,6 +3241,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
249 changes: 152 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,124 @@ 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: "grpc://localhost:50051",
},
want: TypeGRPC,
},
{
args: testEndpoint_typeArgs{
URL: "grpcs://example.com:443",
},
want: TypeGRPC,
},
{
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 +431,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