mirror of https://github.com/statping/statping
Merge 63baf4a83a
into fd889a14c0
commit
bec4351687
|
@ -19,6 +19,7 @@
|
|||
<option value="udp">UDP {{ $t('service') }}</option>
|
||||
<option value="icmp">ICMP Ping</option>
|
||||
<option value="grpc">gRPC {{ $t('service') }}</option>
|
||||
<option value="ssh">SSH {{ $t('service') }}</option>
|
||||
<option value="static">Static {{ $t('service') }}</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">Use HTTP if you are checking a website or use TCP if you are checking a server</small>
|
||||
|
@ -82,7 +83,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.type.match(/^(tcp|udp|grpc)$/)" class="form-group row">
|
||||
<div v-if="service.type.match(/^(tcp|udp|grpc|ssh)$/)" class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Port</label>
|
||||
<div class="col-sm-8">
|
||||
<input v-model.number="service.port" type="number" name="port" class="form-control" id="service_port" placeholder="8080">
|
||||
|
@ -193,6 +194,55 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.type.match(/^(ssh)$/)" class="form-group row">
|
||||
<label for="service_username" class="col-sm-4 col-form-label">{{ $t('username') }}</label>
|
||||
<div class="col-sm-8">
|
||||
<input v-model="service.username" type="text" name="service_username" class="form-control" id="service_username">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.type.match(/^(ssh)$/)" class="form-group row">
|
||||
<label for="service_password" class="col-sm-4 col-form-label">{{ $t('password') }}</label>
|
||||
<div class="col-sm-8">
|
||||
<input v-model="service.password" type="text" name="service_password" class="form-control" id="service_password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.type.match(/^(ssh)$/)" class="form-group row">
|
||||
<label class="col-12 col-md-4 col-form-label">SSH Health Check</label>
|
||||
<div class="col-12 col-md-8 mt-1 mb-2 mb-md-0">
|
||||
<span @click="service.ssh_health_check = !!service.ssh_health_check" class="switch float-left">
|
||||
<input v-model="service.ssh_health_check" type="checkbox" name="ssh_health_check-option" class="switch" id="switch-ssh-health-check" v-bind:checked="service.ssh_health_check">
|
||||
<label for="switch-ssh-health-check" v-if="service.ssh_health_check">Check against SSH health check command.</label>
|
||||
<label for="switch-ssh-health-check" v-if="!service.ssh_health_check">Only checks if SSH connection can be established.</label>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.ssh_health_check" class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Expected Response</label>
|
||||
<div class="col-sm-8">
|
||||
<textarea v-model="service.expected" class="form-control" rows="3" autocapitalize="none" spellcheck="false"></textarea>
|
||||
<small class="form-text text-muted">Only stdout will be parsed</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.ssh_health_check" class="form-group row">
|
||||
<label for="service_response_code" class="col-sm-4 col-form-label">Expected Exit Code</label>
|
||||
<div class="col-sm-8">
|
||||
<input v-model="service.expected_status" type="number" name="expected_status" class="form-control" placeholder="1" id="service_response_code">
|
||||
<small class="form-text text-muted">By convention a command exit code 0 indicates success</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.type.match(/^(ssh)$/) && service.ssh_health_check" class="form-group row">
|
||||
<label for="check_command" class="col-sm-4 col-form-label">Check Command</label>
|
||||
<div class="col-sm-8">
|
||||
<input v-model="service.check_command" type="text" name="check_command" class="form-control" id="check_command">
|
||||
<small class="form-text text-muted">Check command to be executed on remote server</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.type.match(/^(tcp|http)$/)" class="form-group row">
|
||||
<label class="col-12 col-md-4 col-form-label">{{ $t('tls_cert') }}</label>
|
||||
<div class="col-12 col-md-8 mt-1 mb-2 mb-md-0">
|
||||
|
@ -303,6 +353,10 @@
|
|||
order: 1,
|
||||
verify_ssl: true,
|
||||
grpc_health_check: false,
|
||||
ssh_health_check: false,
|
||||
username: "",
|
||||
password: "",
|
||||
check_command: "",
|
||||
redirect: true,
|
||||
allow_notifications: true,
|
||||
notify_all_changes: true,
|
||||
|
@ -351,6 +405,9 @@
|
|||
this.service.port = 50051
|
||||
this.service.verify_ssl = false
|
||||
this.service.method = ""
|
||||
} else if (this.service.type == "ssh") {
|
||||
this.service.port = 22;
|
||||
this.service.expected_status = 0;
|
||||
} else {
|
||||
this.service.expected_status = 200
|
||||
this.service.expected = ""
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/statping/statping/types/metrics"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
|
@ -58,7 +59,7 @@ CheckLoop:
|
|||
}
|
||||
|
||||
func parseHost(s *Service) string {
|
||||
if s.Type == "tcp" || s.Type == "udp" || s.Type == "grpc" {
|
||||
if s.Type == "tcp" || s.Type == "udp" || s.Type == "grpc" || s.Type == "ssh" {
|
||||
return s.Domain
|
||||
} else {
|
||||
u, err := url.Parse(s.Domain)
|
||||
|
@ -74,7 +75,7 @@ func dnsCheck(s *Service) (int64, error) {
|
|||
var err error
|
||||
t1 := utils.Now()
|
||||
host := parseHost(s)
|
||||
if s.Type == "tcp" || s.Type == "udp" || s.Type == "grpc" {
|
||||
if s.Type == "tcp" || s.Type == "udp" || s.Type == "grpc" || s.Type == "ssh" {
|
||||
_, err = net.LookupHost(host)
|
||||
} else {
|
||||
_, err = net.LookupIP(host)
|
||||
|
@ -207,9 +208,9 @@ func CheckGrpc(s *Service, record bool) (*Service, error) {
|
|||
s.Online = true
|
||||
|
||||
if s.GrpcHealthCheck.Bool {
|
||||
if s.ExpectedStatus != s.LastStatusCode {
|
||||
if *s.ExpectedStatus != s.LastStatusCode {
|
||||
if record {
|
||||
RecordFailure(s, fmt.Sprintf("GRPC Service: '%s', Status Code: expected '%v', got '%v'", s.Name, s.ExpectedStatus, s.LastStatusCode), "response_code")
|
||||
RecordFailure(s, fmt.Sprintf("GRPC Service: '%s', Status Code: expected '%v', got '%v'", s.Name, *s.ExpectedStatus, s.LastStatusCode), "response_code")
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
@ -380,9 +381,9 @@ func CheckHttp(s *Service, record bool) (*Service, error) {
|
|||
return s, err
|
||||
}
|
||||
}
|
||||
if s.ExpectedStatus != res.StatusCode {
|
||||
if *s.ExpectedStatus != res.StatusCode {
|
||||
if record {
|
||||
RecordFailure(s, fmt.Sprintf("HTTP Status Code %v did not match %v", res.StatusCode, s.ExpectedStatus), "status_code")
|
||||
RecordFailure(s, fmt.Sprintf("HTTP Status Code %v did not match %v", res.StatusCode, *s.ExpectedStatus), "status_code")
|
||||
}
|
||||
return s, err
|
||||
}
|
||||
|
@ -393,6 +394,97 @@ func CheckHttp(s *Service, record bool) (*Service, error) {
|
|||
return s, err
|
||||
}
|
||||
|
||||
// CheckSsh will check a SSH service
|
||||
func CheckSsh(s *Service, record bool) (*Service, error) {
|
||||
defer s.updateLastCheck()
|
||||
timer := prometheus.NewTimer(metrics.ServiceTimer(s.Name))
|
||||
defer timer.ObserveDuration()
|
||||
|
||||
dnsLookup, err := dnsCheck(s)
|
||||
if err != nil {
|
||||
if record {
|
||||
RecordFailure(s, fmt.Sprintf("Could not get IP address for SSH service %v, %v", s.Domain, err), "lookup")
|
||||
}
|
||||
return s, err
|
||||
}
|
||||
s.PingTime = dnsLookup
|
||||
t1 := utils.Now()
|
||||
domain := fmt.Sprintf("%v", s.Domain)
|
||||
if s.Port != 0 {
|
||||
domain = fmt.Sprintf("%v:%v", s.Domain, s.Port)
|
||||
if isIPv6(s.Domain) {
|
||||
domain = fmt.Sprintf("[%v]:%v", s.Domain, s.Port)
|
||||
}
|
||||
}
|
||||
|
||||
sshConfig := &ssh.ClientConfig{
|
||||
User: s.Username,
|
||||
Auth: []ssh.AuthMethod{ssh.Password(s.Password)},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
Timeout: time.Duration(s.Timeout) * time.Second,
|
||||
}
|
||||
|
||||
client, err := ssh.Dial("tcp", domain, sshConfig)
|
||||
if err != nil {
|
||||
if record {
|
||||
RecordFailure(s, fmt.Sprintf("Dial Error: %v", err), "ssh")
|
||||
}
|
||||
return s, err
|
||||
}
|
||||
|
||||
defer client.Close()
|
||||
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
if record {
|
||||
RecordFailure(s, fmt.Sprintf("Failed to create SSH session: %v", err), "ssh")
|
||||
}
|
||||
return s, err
|
||||
}
|
||||
|
||||
if s.SshHealthCheck.Bool {
|
||||
output, err := session.Output(s.CheckCommand)
|
||||
if err != nil {
|
||||
if exitError, ok := err.(*ssh.ExitError); ok {
|
||||
s.LastStatusCode = exitError.Waitmsg.ExitStatus()
|
||||
} else {
|
||||
if record {
|
||||
RecordFailure(s, fmt.Sprintf("Failed to execute check command: %v", err), "ssh")
|
||||
return s, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.LastResponse = strings.TrimSpace(string(output))
|
||||
}
|
||||
|
||||
s.Latency = utils.Now().Sub(t1).Microseconds()
|
||||
s.Online = true
|
||||
|
||||
if s.SshHealthCheck.Bool {
|
||||
if *s.ExpectedStatus != s.LastStatusCode {
|
||||
if record {
|
||||
RecordFailure(s, fmt.Sprintf("SSH Service: '%s', Exit Code: expected '%v', got '%v'", s.Name, *s.ExpectedStatus, s.LastStatusCode), "response_code")
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
if s.Expected.String != s.LastResponse {
|
||||
log.Warnln(fmt.Sprintf("SSH Service: '%s', Command output: expected '%v', got '%v'", s.Name, s.Expected.String, s.LastResponse))
|
||||
if record {
|
||||
RecordFailure(s, fmt.Sprintf("SSH Command output '%v' did not match '%v'", s.LastResponse, s.Expected.String), "response_body")
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
|
||||
if record {
|
||||
RecordSuccess(s)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// RecordSuccess will create a new 'hit' record in the database for a successful/online service
|
||||
func RecordSuccess(s *Service) {
|
||||
s.LastOnline = utils.Now()
|
||||
|
@ -460,5 +552,7 @@ func (s *Service) CheckService(record bool) {
|
|||
CheckGrpc(s, record)
|
||||
case "icmp":
|
||||
CheckIcmp(s, record)
|
||||
case "ssh":
|
||||
CheckSsh(s, record)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ var testdata = []struct {
|
|||
Domain: "localhost",
|
||||
Port: 50053,
|
||||
Expected: null.NewNullString("status:SERVING"),
|
||||
ExpectedStatus: 1,
|
||||
ExpectedStatus: &[]int{1}[0],
|
||||
Type: "grpc",
|
||||
Timeout: 3,
|
||||
VerifySSL: null.NewNullBool(false),
|
||||
|
@ -47,7 +47,7 @@ var testdata = []struct {
|
|||
Domain: "localhost",
|
||||
Port: 50054,
|
||||
Expected: null.NewNullString(""),
|
||||
ExpectedStatus: 0,
|
||||
ExpectedStatus: &[]int{0}[0],
|
||||
Type: "grpc",
|
||||
Timeout: 1,
|
||||
VerifySSL: null.NewNullBool(true),
|
||||
|
@ -63,7 +63,7 @@ var testdata = []struct {
|
|||
Domain: "localhost",
|
||||
Port: 50055,
|
||||
Expected: null.NewNullString(""),
|
||||
ExpectedStatus: 0,
|
||||
ExpectedStatus: &[]int{0}[0],
|
||||
Type: "grpc",
|
||||
Timeout: 1,
|
||||
VerifySSL: null.NewNullBool(false),
|
||||
|
@ -78,7 +78,7 @@ var testdata = []struct {
|
|||
Domain: "localhost",
|
||||
Port: 1000,
|
||||
Expected: null.NewNullString(""),
|
||||
ExpectedStatus: 0,
|
||||
ExpectedStatus: &[]int{0}[0],
|
||||
Type: "grpc",
|
||||
Timeout: 1,
|
||||
VerifySSL: null.NewNullBool(false),
|
||||
|
@ -94,7 +94,7 @@ var testdata = []struct {
|
|||
Domain: "localhost",
|
||||
Port: 1000,
|
||||
Expected: null.NewNullString(""),
|
||||
ExpectedStatus: 0,
|
||||
ExpectedStatus: &[]int{0}[0],
|
||||
Type: "grpc",
|
||||
Timeout: 1,
|
||||
VerifySSL: null.NewNullBool(true),
|
||||
|
@ -110,7 +110,7 @@ var testdata = []struct {
|
|||
Domain: "http://localhost",
|
||||
Port: 50058,
|
||||
Expected: null.NewNullString("status:SERVING"),
|
||||
ExpectedStatus: 1,
|
||||
ExpectedStatus: &[]int{1}[0],
|
||||
Type: "grpc",
|
||||
Timeout: 1,
|
||||
VerifySSL: null.NewNullBool(false),
|
||||
|
@ -126,7 +126,7 @@ var testdata = []struct {
|
|||
Domain: "http://local//host",
|
||||
Port: 50059,
|
||||
Expected: null.NewNullString(""),
|
||||
ExpectedStatus: 0,
|
||||
ExpectedStatus: &[]int{0}[0],
|
||||
Type: "grpc",
|
||||
Timeout: 1,
|
||||
VerifySSL: null.NewNullBool(false),
|
||||
|
@ -142,7 +142,7 @@ var testdata = []struct {
|
|||
Domain: "https://google.com",
|
||||
Port: 443,
|
||||
Expected: null.NewNullString(""),
|
||||
ExpectedStatus: 0,
|
||||
ExpectedStatus: &[]int{0}[0],
|
||||
Type: "grpc",
|
||||
Timeout: 1,
|
||||
VerifySSL: null.NewNullBool(false),
|
||||
|
@ -157,7 +157,7 @@ var testdata = []struct {
|
|||
Domain: "http://localhost",
|
||||
Port: 50061,
|
||||
Expected: null.NewNullString(""),
|
||||
ExpectedStatus: 0,
|
||||
ExpectedStatus: &[]int{0}[0],
|
||||
Type: "grpc",
|
||||
Timeout: 1,
|
||||
VerifySSL: null.NewNullBool(false),
|
||||
|
@ -194,7 +194,7 @@ func TestCheckGrpc(t *testing.T) {
|
|||
server := v.grpcService(v.clientChecker.Port, v.clientChecker.GrpcHealthCheck.Bool)
|
||||
defer server.Stop()
|
||||
v.clientChecker.CheckService(false)
|
||||
if v.clientChecker.LastStatusCode != v.clientChecker.ExpectedStatus || strings.TrimSpace(v.clientChecker.LastResponse) != v.clientChecker.Expected.String {
|
||||
if v.clientChecker.LastStatusCode != *v.clientChecker.ExpectedStatus || strings.TrimSpace(v.clientChecker.LastResponse) != v.clientChecker.Expected.String {
|
||||
t.Errorf("Expected message: '%v', Got message: '%v' , Expected Status: '%v', Got Status: '%v'", v.clientChecker.Expected.String, v.clientChecker.LastResponse, v.clientChecker.ExpectedStatus, v.clientChecker.LastStatusCode)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -12,7 +12,7 @@ func Example(online bool) Service {
|
|||
Name: "Statping Example",
|
||||
Domain: "https://statping.com",
|
||||
Expected: null.NewNullString(""),
|
||||
ExpectedStatus: 200,
|
||||
ExpectedStatus: &[]int{200}[0],
|
||||
Interval: int(time.Duration(15 * time.Second).Seconds()),
|
||||
Type: "http",
|
||||
Method: "get",
|
||||
|
@ -63,7 +63,7 @@ func Samples() error {
|
|||
s1 := &Service{
|
||||
Name: "Google",
|
||||
Domain: "https://google.com",
|
||||
ExpectedStatus: 200,
|
||||
ExpectedStatus: &[]int{200}[0],
|
||||
Interval: 10,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
|
@ -84,7 +84,7 @@ func Samples() error {
|
|||
s2 := &Service{
|
||||
Name: "Statping Github",
|
||||
Domain: "https://github.com/statping/statping",
|
||||
ExpectedStatus: 200,
|
||||
ExpectedStatus: &[]int{200}[0],
|
||||
Interval: 30,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
|
@ -103,7 +103,7 @@ func Samples() error {
|
|||
s3 := &Service{
|
||||
Name: "JSON Users Test",
|
||||
Domain: "https://jsonplaceholder.typicode.com/users",
|
||||
ExpectedStatus: 200,
|
||||
ExpectedStatus: &[]int{200}[0],
|
||||
Interval: 60,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
|
@ -122,7 +122,7 @@ func Samples() error {
|
|||
s4 := &Service{
|
||||
Name: "JSON API Tester",
|
||||
Domain: "https://jsonplaceholder.typicode.com/posts",
|
||||
ExpectedStatus: 201,
|
||||
ExpectedStatus: &[]int{201}[0],
|
||||
Expected: null.NewNullString(`(title)": "((\\"|[statping])*)"`),
|
||||
Interval: 30,
|
||||
Type: "http",
|
||||
|
|
|
@ -26,7 +26,7 @@ import (
|
|||
var example = &Service{
|
||||
Name: "Example Service",
|
||||
Domain: "https://statping.com",
|
||||
ExpectedStatus: 200,
|
||||
ExpectedStatus: &[]int{200}[0],
|
||||
Interval: 30,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
|
@ -237,7 +237,7 @@ func TestServices(t *testing.T) {
|
|||
e := &Service{
|
||||
Name: "Example HTTP",
|
||||
Domain: "http://localhost:15000",
|
||||
ExpectedStatus: 200,
|
||||
ExpectedStatus: &[]int{200}[0],
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 5,
|
||||
|
@ -255,7 +255,7 @@ func TestServices(t *testing.T) {
|
|||
e := &Service{
|
||||
Name: "Example TLS",
|
||||
Domain: "http://localhost:15001",
|
||||
ExpectedStatus: 200,
|
||||
ExpectedStatus: &[]int{200}[0],
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 5,
|
||||
|
@ -276,7 +276,7 @@ func TestServices(t *testing.T) {
|
|||
e := &Service{
|
||||
Name: "Example TLS HTTP",
|
||||
Domain: "https://localhost:15001",
|
||||
ExpectedStatus: 200,
|
||||
ExpectedStatus: &[]int{200}[0],
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 15,
|
||||
|
@ -487,7 +487,7 @@ func TestServices(t *testing.T) {
|
|||
example := &Service{
|
||||
Name: "Example Service 2",
|
||||
Domain: "https://slack.statping.com",
|
||||
ExpectedStatus: 200,
|
||||
ExpectedStatus: &[]int{200}[0],
|
||||
Interval: 10,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
|
|
|
@ -16,7 +16,7 @@ type Service struct {
|
|||
Name string `gorm:"column:name" json:"name" yaml:"name"`
|
||||
Domain string `gorm:"column:domain" json:"domain" yaml:"domain" private:"true" scope:"user,admin"`
|
||||
Expected null.NullString `gorm:"column:expected" json:"expected" yaml:"expected" scope:"user,admin"`
|
||||
ExpectedStatus int `gorm:"default:200;column:expected_status" json:"expected_status" yaml:"expected_status" scope:"user,admin"`
|
||||
ExpectedStatus *int `gorm:"column:expected_status" json:"expected_status" yaml:"expected_status" scope:"user,admin"`
|
||||
Interval int `gorm:"default:30;column:check_interval" json:"check_interval" yaml:"check_interval"`
|
||||
Type string `gorm:"column:check_type" json:"type" scope:"user,admin" yaml:"type"`
|
||||
Method string `gorm:"column:method" json:"method" scope:"user,admin" yaml:"method"`
|
||||
|
@ -26,6 +26,10 @@ type Service struct {
|
|||
Order int `gorm:"default:0;column:order_id" json:"order_id" yaml:"order_id"`
|
||||
VerifySSL null.NullBool `gorm:"default:false;column:verify_ssl" json:"verify_ssl" scope:"user,admin" yaml:"verify_ssl"`
|
||||
GrpcHealthCheck null.NullBool `gorm:"default:false;column:grpc_health_check" json:"grpc_health_check" scope:"user,admin" yaml:"grpc_health_check"`
|
||||
Username string `gorm:"column:username" json:"username" yaml:"username" scope:"user,admin"`
|
||||
Password string `gorm:"column:password" json:"password" yaml:"password" scope:"user,admin"`
|
||||
CheckCommand string `gorm:"column:check_command" json:"check_command" yaml:"check_command" scope:"user,admin"`
|
||||
SshHealthCheck null.NullBool `gorm:"default:false;column:ssh_health_check" json:"ssh_health_check" scope:"user,admin" yaml:"ssh_health_check"`
|
||||
Public null.NullBool `gorm:"default:true;column:public" json:"public" yaml:"public"`
|
||||
GroupId int `gorm:"default:0;column:group_id" json:"group_id" yaml:"group_id"`
|
||||
TLSCert null.NullString `gorm:"column:tls_cert" json:"tls_cert" scope:"user,admin" yaml:"tls_cert"`
|
||||
|
|
Loading…
Reference in New Issue