Implement CheckCle Microservice Operation

A Go-based microservice for service operations including ICMP ping, DNS resolution, and TCP connectivity
This commit is contained in:
Tola Leng
2025-06-19 17:49:30 +07:00
parent 89572c44b7
commit d3ea5dc77c
36 changed files with 2895 additions and 0 deletions
@@ -0,0 +1,52 @@
package pocketbase
import (
"fmt"
"net/http"
"time"
)
type PocketBaseClient struct {
baseURL string
httpClient *http.Client
}
func NewPocketBaseClient(baseURL string) (*PocketBaseClient, error) {
// Use provided baseURL or default to localhost
if baseURL == "" {
baseURL = "http://localhost:8090"
}
client := &PocketBaseClient{
baseURL: baseURL,
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
}
return client, nil
}
func (c *PocketBaseClient) GetBaseURL() string {
return c.baseURL
}
func (c *PocketBaseClient) TestConnection() error {
resp, err := c.httpClient.Get(fmt.Sprintf("%s/api/health", c.baseURL))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("PocketBase health check failed with status: %d", resp.StatusCode)
}
return nil
}
func (c *PocketBaseClient) IsAuthenticated() bool {
// Since we're using public access mode, always return true
return true
}
@@ -0,0 +1,22 @@
package pocketbase
func (c *PocketBaseClient) SaveMetrics(metrics MetricsRecord) error {
return c.createRecord("services_metrics", metrics)
}
func (c *PocketBaseClient) SavePingData(pingData PingDataRecord) error {
return c.createRecord("ping_data", pingData)
}
func (c *PocketBaseClient) SaveUptimeData(uptimeData UptimeDataRecord) error {
return c.createRecord("uptime_data", uptimeData)
}
func (c *PocketBaseClient) SaveDNSData(dnsData DNSDataRecord) error {
return c.createRecord("dns_data", dnsData)
}
func (c *PocketBaseClient) SaveTCPData(tcpData TCPDataRecord) error {
return c.createRecord("tcp_data", tcpData)
}
@@ -0,0 +1,138 @@
package pocketbase
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
)
func (c *PocketBaseClient) GetServices() ([]Service, error) {
req, err := http.NewRequest("GET",
fmt.Sprintf("%s/api/collections/services/records", c.baseURL), nil)
if err != nil {
return nil, err
}
// No authentication header needed for public access
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to fetch services, status: %d", resp.StatusCode)
}
var servicesResponse ServicesResponse
if err := json.NewDecoder(resp.Body).Decode(&servicesResponse); err != nil {
return nil, err
}
return servicesResponse.Items, nil
}
func (c *PocketBaseClient) GetService(serviceID string) (*Service, error) {
req, err := http.NewRequest("GET",
fmt.Sprintf("%s/api/collections/services/records/%s", c.baseURL, serviceID), nil)
if err != nil {
return nil, err
}
// No authentication header needed for public access
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to fetch service %s, status: %d", serviceID, resp.StatusCode)
}
var service Service
if err := json.NewDecoder(resp.Body).Decode(&service); err != nil {
return nil, err
}
return &service, nil
}
func (c *PocketBaseClient) GetActiveServices() ([]Service, error) {
// Only fetch services that are not paused
req, err := http.NewRequest("GET",
fmt.Sprintf("%s/api/collections/services/records?filter=(status!='paused')", c.baseURL), nil)
if err != nil {
return nil, err
}
// No authentication header needed for public access
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to fetch active services, status: %d", resp.StatusCode)
}
var servicesResponse ServicesResponse
if err := json.NewDecoder(resp.Body).Decode(&servicesResponse); err != nil {
return nil, err
}
return servicesResponse.Items, nil
}
func (c *PocketBaseClient) UpdateServiceStatus(serviceID string, status string, responseTime int64, errorMessage string) error {
// First check if the service is paused before updating
service, err := c.GetService(serviceID)
if err != nil {
return fmt.Errorf("failed to check service status before update: %v", err)
}
if service.Status == "paused" {
return fmt.Errorf("service %s is paused, skipping status update", serviceID)
}
updateData := map[string]interface{}{
"status": status,
"response_time": responseTime,
"last_checked": time.Now().Format(time.RFC3339),
}
if errorMessage != "" {
updateData["error_message"] = errorMessage
}
jsonData, err := json.Marshal(updateData)
if err != nil {
return err
}
req, err := http.NewRequest("PATCH",
fmt.Sprintf("%s/api/collections/services/records/%s", c.baseURL, serviceID),
bytes.NewBuffer(jsonData))
if err != nil {
return err
}
// No authentication header needed
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
return fmt.Errorf("failed to update service status, status: %d", resp.StatusCode)
}
return nil
}
@@ -0,0 +1,126 @@
package pocketbase
import "time"
type AuthResponse struct {
Token string `json:"token"`
Record interface{} `json:"record"`
}
type MetricsRecord struct {
ServiceName string `json:"service_name"`
Host string `json:"host"`
Uptime float64 `json:"uptime"`
ResponseTime int64 `json:"response_time"`
LastChecked string `json:"last_checked"`
Port int `json:"port,omitempty"`
Domain string `json:"domain,omitempty"`
HeartbeatInterval int `json:"heartbeat_interval,omitempty"`
MaxRetries int `json:"max_retries,omitempty"`
NotificationID string `json:"notification_id,omitempty"`
TemplateID string `json:"template_id,omitempty"`
ServiceType string `json:"service_type"`
Status string `json:"status"`
URL string `json:"url,omitempty"`
Alerts string `json:"alerts,omitempty"`
StatusCodes string `json:"status_codes,omitempty"`
Keyword string `json:"keyword,omitempty"`
ErrorMessage string `json:"error_message,omitempty"`
Details string `json:"details,omitempty"`
CheckedAt string `json:"checked_at"`
}
type PingDataRecord struct {
ServiceID string `json:"service_id"`
Timestamp time.Time `json:"timestamp"`
ResponseTime int64 `json:"response_time"`
Status string `json:"status"`
PacketLoss string `json:"packet_loss"`
Latency string `json:"latency"`
MaxRTT string `json:"max_rtt"`
MinRTT string `json:"min_rtt"`
PacketsSent string `json:"packets_sent"`
PacketsRecv string `json:"packets_recv"`
AvgRTT string `json:"avg_rtt"`
RTTs string `json:"rtts"`
Details string `json:"details,omitempty"`
ErrorMessage string `json:"error_message,omitempty"`
}
type UptimeDataRecord struct {
ServiceID string `json:"service_id"`
Timestamp time.Time `json:"timestamp"`
ResponseTime int64 `json:"response_time"`
Status string `json:"status"`
Packets string `json:"packets"`
Latency string `json:"latency"`
StatusCodes string `json:"status_codes"`
Keyword string `json:"keyword"`
ErrorMessage string `json:"error_message"`
Details string `json:"details"`
Region string `json:"region,omitempty"`
RegionID string `json:"region_id,omitempty"`
}
type DNSDataRecord struct {
ServiceID string `json:"service_id"`
Timestamp time.Time `json:"timestamp"`
ResponseTime int64 `json:"response_time"`
Status string `json:"status"`
QueryType string `json:"query_type"`
ResolveIP string `json:"resolve_ip"`
MsgSize string `json:"msg_size"`
Question string `json:"question"`
Answer string `json:"answer"`
Authority string `json:"authority"`
ErrorMessage string `json:"error_message,omitempty"`
Details string `json:"details,omitempty"`
RegionName string `json:"region_name,omitempty"`
AgentID string `json:"agent_id,omitempty"`
}
type TCPDataRecord struct {
ServiceID string `json:"service_id"`
Timestamp time.Time `json:"timestamp"`
ResponseTime int64 `json:"response_time"`
Status string `json:"status"`
Connection string `json:"connection"`
Latency string `json:"latency"`
Port string `json:"port"`
ErrorMessage string `json:"error_message,omitempty"`
Details string `json:"details,omitempty"`
RegionName string `json:"region_name,omitempty"`
AgentID string `json:"agent_id,omitempty"`
}
type Service struct {
ID string `json:"id"`
Name string `json:"name"`
Host string `json:"host"`
Uptime float64 `json:"uptime"`
ResponseTime int64 `json:"response_time"`
LastChecked string `json:"last_checked"`
Port int `json:"port"`
Domain string `json:"domain"`
HeartbeatInterval int `json:"heartbeat_interval"`
MaxRetries int `json:"max_retries"`
NotificationID string `json:"notification_id"`
TemplateID string `json:"template_id"`
ServiceType string `json:"service_type"`
Status string `json:"status"`
URL string `json:"url"`
Alerts string `json:"alerts"`
StatusCodes string `json:"status_codes"`
Keyword string `json:"keyword"`
Created string `json:"created"`
Updated string `json:"updated"`
}
type ServicesResponse struct {
Page int `json:"page"`
PerPage int `json:"perPage"`
TotalItems int `json:"totalItems"`
TotalPages int `json:"totalPages"`
Items []Service `json:"items"`
}
@@ -0,0 +1,47 @@
package pocketbase
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func (c *PocketBaseClient) createRecord(collection string, data interface{}) error {
jsonData, err := json.Marshal(data)
if err != nil {
return err
}
req, err := http.NewRequest("POST",
fmt.Sprintf("%s/api/collections/%s/records", c.baseURL, collection),
bytes.NewBuffer(jsonData))
if err != nil {
return err
}
// No authentication header needed
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
// Read response body for better error details
bodyBytes, _ := io.ReadAll(resp.Body)
bodyString := string(bodyBytes)
fmt.Printf("Failed to create record in %s collection. Status: %d, Response: %s\n",
collection, resp.StatusCode, bodyString)
return fmt.Errorf("failed to create record in %s, status: %d, response: %s",
collection, resp.StatusCode, bodyString)
}
return nil
}