From ed8f5d737fc553ab1a6f02b5ccd35eaff3a4e973 Mon Sep 17 00:00:00 2001 From: Didier SEGURA Date: Thu, 17 Apr 2025 10:43:37 +0200 Subject: [PATCH 1/3] + Add UptimeRobot all monitors features --- pkg/monitors/uptimerobot/uptime-monitor.go | 145 +++++++++++-------- pkg/monitors/uptimerobot/uptime-responses.go | 36 +++-- 2 files changed, 108 insertions(+), 73 deletions(-) diff --git a/pkg/monitors/uptimerobot/uptime-monitor.go b/pkg/monitors/uptimerobot/uptime-monitor.go index 838a67d7..5ed6496e 100644 --- a/pkg/monitors/uptimerobot/uptime-monitor.go +++ b/pkg/monitors/uptimerobot/uptime-monitor.go @@ -9,7 +9,6 @@ import ( "reflect" "strconv" "strings" - "time" endpointmonitorv1alpha1 "github.com/stakater/IngressMonitorController/v2/api/v1alpha1" "github.com/stakater/IngressMonitorController/v2/pkg/config" @@ -68,17 +67,6 @@ func (monitor *UpTimeMonitorService) GetByName(name string) (*models.Monitor, er } return nil, nil - } else if response.StatusCode == Http.StatusTooManyRequests { - log.Info("Too many requests, Monitor waiting for timeout: " + name) - retryAfter := response.Header.Get("Retry-After") - if retryAfter != "" { - seconds, err := strconv.Atoi(retryAfter) - if err == nil { - time.Sleep(time.Duration(seconds) * time.Second) - return monitor.GetByName(name) // Retry after the specified delay - - } - } } errorString := "GetByName Request failed for name: " + name + ". Status Code: " + strconv.Itoa(response.StatusCode) @@ -164,16 +152,6 @@ func (monitor *UpTimeMonitorService) Add(m models.Monitor) { } else { log.Info("Monitor couldn't be added: " + m.Name + ". Error: " + f.Error.Message) } - } else if response.StatusCode == Http.StatusTooManyRequests { - log.Info("Too many requests, Monitor waiting for timeout: " + m.Name) - retryAfter := response.Header.Get("Retry-After") - if retryAfter != "" { - seconds, err := strconv.Atoi(retryAfter) - if err == nil { - time.Sleep(time.Duration(seconds) * time.Second) - monitor.Add(m) // Retry after the specified delay - } - } } else { log.Info("AddMonitor Request failed. Status Code: " + strconv.Itoa(response.StatusCode)) } @@ -200,16 +178,6 @@ func (monitor *UpTimeMonitorService) Update(m models.Monitor) { } else { log.Info("Monitor couldn't be updated: " + m.Name + ". Error: " + f.Error.Message) } - } else if response.StatusCode == Http.StatusTooManyRequests { - log.Info("Too many requests, Monitor waiting for timeout: " + m.Name) - retryAfter := response.Header.Get("Retry-After") - if retryAfter != "" { - seconds, err := strconv.Atoi(retryAfter) - if err == nil { - time.Sleep(time.Duration(seconds) * time.Second) - monitor.Update(m) // Retry after the specified delay - } - } } else { log.Info("UpdateMonitor Request failed. Status Code: " + strconv.Itoa(response.StatusCode)) } @@ -228,27 +196,7 @@ func (monitor *UpTimeMonitorService) processProviderConfig(m models.Monitor, cre // Retrieve provider configuration providerConfig, _ := m.Config.(*endpointmonitorv1alpha1.UptimeRobotConfig) - if providerConfig != nil && len(providerConfig.AlertContacts) != 0 { - body += "&alert_contacts=" + providerConfig.AlertContacts - } else { - body += "&alert_contacts=" + monitor.alertContacts - } - - if providerConfig != nil && providerConfig.Interval > 0 { - body += "&interval=" + strconv.Itoa(providerConfig.Interval) - } else { - // Uptime robot adds a default interval of 5 minutes, if it is not specified - body += "&interval=" + strconv.Itoa(DefaultInterval) - } - - if providerConfig != nil && len(providerConfig.MaintenanceWindows) != 0 { - body += "&mwindows=" + providerConfig.MaintenanceWindows - } - - if providerConfig != nil && len(providerConfig.CustomHTTPStatuses) != 0 { - body += "&custom_http_statuses=" + providerConfig.CustomHTTPStatuses - } - + // Type (Required) if providerConfig != nil && len(providerConfig.MonitorType) != 0 { if strings.Contains(strings.ToLower(providerConfig.MonitorType), "http") { body += "&type=1" @@ -256,19 +204,18 @@ func (monitor *UpTimeMonitorService) processProviderConfig(m models.Monitor, cre body += "&type=2" if providerConfig != nil && len(providerConfig.KeywordExists) != 0 { - if strings.Contains(strings.ToLower(providerConfig.KeywordExists), "yes") { body += "&keyword_type=1" } else if strings.Contains(strings.ToLower(providerConfig.KeywordExists), "no") { body += "&keyword_type=2" } - } else { body += "&keyword_type=1" // By default 1 (check if keyword exists) } + // Keyword Value (Required for keyword monitoring) if providerConfig != nil && len(providerConfig.KeywordValue) != 0 { - body += "&keyword_value=" + providerConfig.KeywordValue + body += "&keyword_value=" + url.QueryEscape(providerConfig.KeywordValue) } else { log.Error(nil, "Monitor is of type Keyword but the `keyword-value` is missing") } @@ -276,6 +223,90 @@ func (monitor *UpTimeMonitorService) processProviderConfig(m models.Monitor, cre } else { body += "&type=1" // By default monitor is of type HTTP } + + // SubType (Optional for certain types) + if providerConfig != nil && len(providerConfig.SubType) != 0 { + body += "&sub_type=" + url.QueryEscape(providerConfig.SubType) + } + + // Port (Optional for certain types) + if providerConfig != nil && providerConfig.Port > 0 { + body += "&port=" + strconv.Itoa(providerConfig.Port) + } + + // Interval (Optional, in seconds) + if providerConfig != nil && providerConfig.Interval > 0 { + body += "&interval=" + strconv.Itoa(providerConfig.Interval) + } else { + body += "&interval=" + strconv.Itoa(DefaultInterval) + } + + // Timeout (Optional, in seconds) + if providerConfig != nil && providerConfig.Timeout > 0 { + body += "&timeout=" + strconv.Itoa(providerConfig.Timeout) + } + + // HTTP Auth (Optional) + if providerConfig != nil && len(providerConfig.HTTPAuthUsername) != 0 && len(providerConfig.HTTPAuthPassword) != 0 { + body += "&http_username=" + url.QueryEscape(providerConfig.HTTPAuthUsername) + body += "&http_password=" + url.QueryEscape(providerConfig.HTTPAuthPassword) + if providerConfig.HTTPAuthType > 0 { + body += "&http_auth_type=" + strconv.Itoa(providerConfig.HTTPAuthType) + } + } + + // Post Type (Optional) + if providerConfig != nil && len(providerConfig.PostType) != 0 { + body += "&post_type=" + url.QueryEscape(providerConfig.PostType) + } + + // Post Value (Optional) + if providerConfig != nil && len(providerConfig.PostValue) != 0 { + body += "&post_value=" + url.QueryEscape(providerConfig.PostValue) + } + + // HTTP Method (Optional) + if providerConfig != nil && len(providerConfig.HTTPMethod) != 0 { + body += "&http_method=" + url.QueryEscape(providerConfig.HTTPMethod) + } + + // Post Content Type (Optional) + if providerConfig != nil && len(providerConfig.PostContentType) != 0 { + body += "&post_content_type=" + url.QueryEscape(providerConfig.PostContentType) + } + + // Alert Contacts (Optional) + if providerConfig != nil && len(providerConfig.AlertContacts) != 0 { + body += "&alert_contacts=" + url.QueryEscape(providerConfig.AlertContacts) + } else { + body += "&alert_contacts=" + url.QueryEscape(monitor.alertContacts) + } + + // Maintenance Windows (Optional) + if providerConfig != nil && len(providerConfig.MaintenanceWindows) != 0 { + body += "&mwindows=" + url.QueryEscape(providerConfig.MaintenanceWindows) + } + + // Custom HTTP Headers (Optional, must be sent as a JSON object) + if providerConfig != nil && len(providerConfig.CustomHTTPHeaders) != 0 { + body += "&custom_http_headers=" + url.QueryEscape(providerConfig.CustomHTTPHeaders) + } + + // Custom HTTP Statuses (Optional, must be sent in specific format) + if providerConfig != nil && len(providerConfig.CustomHTTPStatuses) != 0 { + body += "&custom_http_statuses=" + url.QueryEscape(providerConfig.CustomHTTPStatuses) + } + + // Ignore SSL Errors (Optional) + if providerConfig != nil && providerConfig.IgnoreSSLErrors > 0 { + body += "&ignore_ssl_errors=" + strconv.Itoa(providerConfig.IgnoreSSLErrors) + } + + // Disable Domain Expire Notifications (Optional) + if providerConfig != nil && providerConfig.DisableDomainExpireNotifications > 0 { + body += "&disable_domain_expire_notifications=" + strconv.Itoa(providerConfig.DisableDomainExpireNotifications) + } + return body } @@ -324,4 +355,4 @@ func (monitor *UpTimeMonitorService) updateStatusPages(statusPages string, monit if err != nil { log.Info("Monitor couldn't be added to status page: " + err.Error()) } -} +} \ No newline at end of file diff --git a/pkg/monitors/uptimerobot/uptime-responses.go b/pkg/monitors/uptimerobot/uptime-responses.go index bd2e9d05..429fc56a 100644 --- a/pkg/monitors/uptimerobot/uptime-responses.go +++ b/pkg/monitors/uptimerobot/uptime-responses.go @@ -13,21 +13,25 @@ type UptimeMonitorPagination struct { } type UptimeMonitorMonitor struct { - ID int `json:"id"` - FriendlyName string `json:"friendly_name"` - URL string `json:"url"` - Type int `json:"type"` - SubType string `json:"sub_type"` - KeywordType int `json:"keyword_type"` - KeywordValue string `json:"keyword_value"` - HTTPUsername string `json:"http_username"` - HTTPPassword string `json:"http_password"` - Port string `json:"port"` - Interval int `json:"interval"` - Status int `json:"status"` - CreateDatetime int `json:"create_datetime"` - Logs []UptimeMonitorLogs `json:"logs"` - AlertContacts []UptimeMonitorAlertContacts `json:"alert_contacts"` + ID int `json:"id"` + FriendlyName string `json:"friendly_name"` + URL string `json:"url"` + Type int `json:"type"` + SubType string `json:"sub_type"` + KeywordType int `json:"keyword_type"` + KeywordValue string `json:"keyword_value"` + HTTPUsername string `json:"http_username"` + HTTPPassword string `json:"http_password"` + Port string `json:"port"` + Interval int `json:"interval"` + Status int `json:"status"` + CreateDatetime int `json:"create_datetime"` + Logs []UptimeMonitorLogs `json:"logs"` + AlertContacts []UptimeMonitorAlertContacts `json:"alert_contacts"` + SSL int `json:"ssl"` + Timeout int `json:"timeout"` + CustomHTTPStatuses string `json:"custom_http_statuses"` + CustomHeader string `json:"custom_header"` } type UptimeMonitorAlertContacts struct { @@ -91,4 +95,4 @@ type UptimeStatusPagesResponse struct { Total int `json:"total"` } `json:"pagination"` StatusPages []UptimePublicStatusPage `json:"psps"` -} +} \ No newline at end of file From e7b42e73132ddab42f0add594b6f7e7af8a6ede9 Mon Sep 17 00:00:00 2001 From: Didier SEGURA Date: Wed, 23 Apr 2025 17:41:13 +0200 Subject: [PATCH 2/3] + Add missing types in UptimeRobotConfig --- api/v1alpha1/endpointmonitor_types.go | 39 +++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/api/v1alpha1/endpointmonitor_types.go b/api/v1alpha1/endpointmonitor_types.go index 321127bd..ccf0be55 100644 --- a/api/v1alpha1/endpointmonitor_types.go +++ b/api/v1alpha1/endpointmonitor_types.go @@ -113,6 +113,45 @@ type UptimeRobotConfig struct { // Defines which http status codes are treated as up or down // For ex: 200:0_401:1_503:1 (to accept 200 as down and 401 and 503 as up) CustomHTTPStatuses string `json:"customHTTPStatuses,omitempty"` + + // Optional sub-type for HTTP monitors (e.g. "HTTPS", "ping", etc.) + SubType string `json:"subType,omitempty"` + + // Optional TCP/UDP port to use when applicable (e.g. 443 for HTTPS) + Port int `json:"port,omitempty"` + + // Timeout in seconds before considering the monitored site unresponsive + Timeout int `json:"timeout,omitempty"` + + // Username for basic HTTP authentication if required by the target URL + HTTPAuthUsername string `json:"httpAuthUsername,omitempty"` + + // Password for basic HTTP authentication + HTTPAuthPassword string `json:"httpAuthPassword,omitempty"` + + // Authentication type: 1 = Basic Auth, 2 = Digest Auth + HTTPAuthType int `json:"httpAuthType,omitempty"` + + // Type of HTTP POST request: e.g., "application/x-www-form-urlencoded" + PostType string `json:"postType,omitempty"` + + // POST body value to be submitted with the request + PostValue string `json:"postValue,omitempty"` + + // HTTP method used for the check: e.g., "GET", "POST" + HTTPMethod string `json:"httpMethod,omitempty"` + + // Content-Type of the HTTP POST request (e.g. "application/json") + PostContentType string `json:"postContentType,omitempty"` + + // Set of custom HTTP headers in JSON format (e.g. {"Authorization": "Bearer TOKEN"}) + CustomHTTPHeaders string `json:"customHttpHeaders,omitempty"` + + // If set to 1, SSL errors will be ignored for HTTPS monitors + IgnoreSSLErrors int `json:"ignoreSSLErrors,omitempty"` + + // If set to 1, disables expiration notifications for domains + DisableDomainExpireNotifications int `json:"disableDomainExpireNotifications,omitempty"` } // UptimeConfig defines the configuration for Uptime Monitor Provider From cc0d1efbe6c7ca3a30fe394e831b5f2672588586 Mon Sep 17 00:00:00 2001 From: Didier SEGURA Date: Thu, 24 Apr 2025 13:51:05 +0200 Subject: [PATCH 3/3] + Rollback missing part (my bad) --- pkg/monitors/uptimerobot/uptime-monitor.go | 54 +++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/pkg/monitors/uptimerobot/uptime-monitor.go b/pkg/monitors/uptimerobot/uptime-monitor.go index 5ed6496e..2abd8e06 100644 --- a/pkg/monitors/uptimerobot/uptime-monitor.go +++ b/pkg/monitors/uptimerobot/uptime-monitor.go @@ -9,6 +9,7 @@ import ( "reflect" "strconv" "strings" + "time" endpointmonitorv1alpha1 "github.com/stakater/IngressMonitorController/v2/api/v1alpha1" "github.com/stakater/IngressMonitorController/v2/pkg/config" @@ -67,6 +68,17 @@ func (monitor *UpTimeMonitorService) GetByName(name string) (*models.Monitor, er } return nil, nil + } else if response.StatusCode == Http.StatusTooManyRequests { + log.Info("Too many requests, Monitor waiting for timeout: " + name) + retryAfter := response.Header.Get("Retry-After") + if retryAfter != "" { + seconds, err := strconv.Atoi(retryAfter) + if err == nil { + time.Sleep(time.Duration(seconds) * time.Second) + return monitor.GetByName(name) // Retry after the specified delay + + } + } } errorString := "GetByName Request failed for name: " + name + ". Status Code: " + strconv.Itoa(response.StatusCode) @@ -152,6 +164,16 @@ func (monitor *UpTimeMonitorService) Add(m models.Monitor) { } else { log.Info("Monitor couldn't be added: " + m.Name + ". Error: " + f.Error.Message) } + } else if response.StatusCode == Http.StatusTooManyRequests { + log.Info("Too many requests, Monitor waiting for timeout: " + m.Name) + retryAfter := response.Header.Get("Retry-After") + if retryAfter != "" { + seconds, err := strconv.Atoi(retryAfter) + if err == nil { + time.Sleep(time.Duration(seconds) * time.Second) + monitor.Add(m) // Retry after the specified delay + } + } } else { log.Info("AddMonitor Request failed. Status Code: " + strconv.Itoa(response.StatusCode)) } @@ -178,6 +200,16 @@ func (monitor *UpTimeMonitorService) Update(m models.Monitor) { } else { log.Info("Monitor couldn't be updated: " + m.Name + ". Error: " + f.Error.Message) } + } else if response.StatusCode == Http.StatusTooManyRequests { + log.Info("Too many requests, Monitor waiting for timeout: " + m.Name) + retryAfter := response.Header.Get("Retry-After") + if retryAfter != "" { + seconds, err := strconv.Atoi(retryAfter) + if err == nil { + time.Sleep(time.Duration(seconds) * time.Second) + monitor.Update(m) // Retry after the specified delay + } + } } else { log.Info("UpdateMonitor Request failed. Status Code: " + strconv.Itoa(response.StatusCode)) } @@ -196,7 +228,27 @@ func (monitor *UpTimeMonitorService) processProviderConfig(m models.Monitor, cre // Retrieve provider configuration providerConfig, _ := m.Config.(*endpointmonitorv1alpha1.UptimeRobotConfig) - // Type (Required) + if providerConfig != nil && len(providerConfig.AlertContacts) != 0 { + body += "&alert_contacts=" + providerConfig.AlertContacts + } else { + body += "&alert_contacts=" + monitor.alertContacts + } + + if providerConfig != nil && providerConfig.Interval > 0 { + body += "&interval=" + strconv.Itoa(providerConfig.Interval) + } else { + // Uptime robot adds a default interval of 5 minutes, if it is not specified + body += "&interval=" + strconv.Itoa(DefaultInterval) + } + + if providerConfig != nil && len(providerConfig.MaintenanceWindows) != 0 { + body += "&mwindows=" + providerConfig.MaintenanceWindows + } + + if providerConfig != nil && len(providerConfig.CustomHTTPStatuses) != 0 { + body += "&custom_http_statuses=" + providerConfig.CustomHTTPStatuses + } + if providerConfig != nil && len(providerConfig.MonitorType) != 0 { if strings.Contains(strings.ToLower(providerConfig.MonitorType), "http") { body += "&type=1"