Openstatus www.openstatus.dev

dns checker (#1528)

* dns checker

* add assertions

* improve dns checker

* ci: apply automated fixes

* generate proto

* clean dns

* add dns post api

* ci: apply automated fixes

* add api for dns monitor

* ci: apply automated fixes

* fix tests

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

authored by

Thibault Le Ouay
autofix-ci[bot]
and committed by
GitHub
67514c1a 2e34e6e2

+3105 -259
+138
apps/checker/checker/dns.go
··· 1 + package checker 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "net" 7 + 8 + "github.com/rs/zerolog/log" 9 + ) 10 + 11 + 12 + type DnsResponse struct { 13 + A []string `json:"a,omitempty"` 14 + AAAA []string `json:"aaaa,omitempty"` 15 + CNAME string `json:"cname,omitempty"` 16 + MX []string `json:"mx,omitempty"` 17 + NS []string `json:"ns,omitempty"` 18 + TXT []string `json:"txt,omitempty"` 19 + } 20 + 21 + func Dns(ctx context.Context, host string) (*DnsResponse, error) { 22 + logger:= log.Ctx(ctx).With().Str("monitor", host).Logger() 23 + 24 + A, err := lookupA(host) 25 + if err != nil { 26 + logger.Error().Err(err).Msg("DNS A record lookup failed") 27 + return nil, fmt.Errorf("failed to lookup A record: %w", err) 28 + } 29 + AAAA, err := lookupAAAA(host) 30 + if err != nil { 31 + logger.Error().Err(err).Msg("DNS AAAA record lookup failed") 32 + return nil, fmt.Errorf("failed to lookup AAAA record: %w", err) 33 + } 34 + CNAME,err := lookupCNAME(host) 35 + if err != nil { 36 + logger.Error().Err(err).Msg("DNS CNAME record lookup failed") 37 + return nil, fmt.Errorf("failed to lookup CNAME record: %w", err) 38 + } 39 + MXRecords := lookupMX(host) 40 + 41 + NS,err := lookupNS(host) 42 + if err != nil { 43 + logger.Error().Err(err).Msg("DNS NS record lookup failed") 44 + return nil, fmt.Errorf("failed to lookup NS record: %w", err) 45 + } 46 + TXT := lookupTXT(host) 47 + 48 + 49 + response := &DnsResponse{ 50 + A: A, 51 + AAAA: AAAA, 52 + CNAME: CNAME, 53 + MX: MXRecords, 54 + NS: NS, 55 + TXT: TXT, 56 + } 57 + 58 + return response, nil 59 + } 60 + 61 + func lookupA(domain string) ([]string, error) { 62 + 63 + A := []string{} 64 + ips, err := net.LookupIP(domain) 65 + if err != nil { 66 + return nil, err 67 + } 68 + 69 + for _, ip := range ips { 70 + if ip.To4() != nil { 71 + A = append(A, ip.String()) 72 + } 73 + } 74 + return A, nil 75 + } 76 + 77 + func lookupAAAA(domain string) ([]string, error) { 78 + AAAA := []string{} 79 + ips, err := net.LookupIP(domain) 80 + if err != nil { 81 + return nil, err 82 + } 83 + 84 + for _, ip := range ips { 85 + if ip.To16() != nil && ip.To4() == nil { 86 + 87 + AAAA = append(AAAA, ip.String()) 88 + } 89 + } 90 + return AAAA, nil 91 + } 92 + 93 + func lookupCNAME(domain string) (string, error) { 94 + cname, err := net.LookupCNAME(domain) 95 + if err != nil { 96 + return "", err 97 + } 98 + 99 + return cname, nil 100 + } 101 + 102 + func lookupMX(domain string) ([]string) { 103 + mx := []string{} 104 + mxRecords,_ := net.LookupMX(domain) 105 + 106 + 107 + for _, r := range mxRecords { 108 + mx = append(mx, fmt.Sprintf("%s:%d", r.Host, r.Pref)) 109 + } 110 + return mx 111 + } 112 + 113 + func lookupNS(domain string) ([]string, error) { 114 + 115 + hosts := []string{} 116 + nsRecords, err := net.LookupNS(domain) 117 + if err != nil { 118 + return nil, err 119 + } 120 + 121 + for _, ns := range nsRecords { 122 + hosts = append(hosts, ns.Host) 123 + } 124 + return hosts, nil 125 + } 126 + 127 + func lookupTXT(domain string) ([]string) { 128 + records := []string{} 129 + txtRecords, err := net.LookupTXT(domain) 130 + if err != nil { 131 + return nil 132 + } 133 + 134 + for _, txt := range txtRecords { 135 + records = append(records, txt) 136 + } 137 + return records 138 + }
+19
apps/checker/checker/dns_test.go
··· 1 + package checker_test 2 + 3 + import ( 4 + "testing" 5 + 6 + "github.com/openstatushq/openstatus/apps/checker/checker" 7 + ) 8 + 9 + 10 + func TestPingDNS(t *testing.T) { 11 + ctx := t.Context() 12 + data, err := checker.Dns(ctx, "openstat.us") 13 + if err != nil { 14 + t.Errorf("Dns() error = %v", err) 15 + } 16 + if len(data.A) == 0 { 17 + t.Errorf("Dns() A records = %v", data.A) 18 + } 19 + }
+2
apps/checker/cmd/server/main.go
··· 70 70 router.POST("/checker", h.HTTPCheckerHandler) 71 71 router.POST("/checker/http", h.HTTPCheckerHandler) 72 72 router.POST("/checker/tcp", h.TCPHandler) 73 + router.POST("/checker/dns", h.DNSHandler) 73 74 router.POST("/ping/:region", h.PingRegionHandler) 74 75 router.POST("/tcp/:region", h.TCPHandlerRegion) 76 + router.POST("/dns/:region", h.DNSHandlerRegion) 75 77 76 78 router.GET("/health", func(c *gin.Context) { 77 79 c.JSON(http.StatusOK, gin.H{"message": "pong", "region": region, "provider": cloudProvider})
+46 -45
apps/checker/handlers/checker.go
··· 46 46 47 47 func (h Handler) HTTPCheckerHandler(c *gin.Context) { 48 48 ctx := c.Request.Context() 49 + const defaultRetry = 3 49 50 dataSourceName := "ping_response__v8" 50 51 51 52 if c.GetHeader("Authorization") != fmt.Sprintf("Basic %s", h.Secret) { ··· 111 112 112 113 var result checker.Response 113 114 114 - var retry int 115 - if req.Retry == 0 { 115 + retry := defaultRetry 116 + if req.Retry != 0 { 116 117 retry = int(req.Retry) 117 - } else { 118 - retry = 3 119 118 } 120 119 121 120 op := func() error { ··· 170 169 RequestStatus: requestStatus, 171 170 } 172 171 173 - statusCode := statusCode(res.Status) 174 - 175 172 var isSuccessfull bool = true 176 - if len(req.RawAssertions) > 0 { 177 - for _, a := range req.RawAssertions { 178 - var assert request.Assertion 179 - err = json.Unmarshal(a, &assert) 180 - if err != nil { 181 - // handle error 182 - return fmt.Errorf("unable to unmarshal assertion: %w", err) 183 - } 184 - 185 - switch assert.AssertionType { 186 - case request.AssertionHeader: 187 - var target assertions.HeaderTarget 188 - if err := json.Unmarshal(a, &target); err != nil { 189 - return fmt.Errorf("unable to unmarshal IntTarget: %w", err) 190 - } 191 - 192 - isSuccessfull = isSuccessfull && target.HeaderEvaluate(data.Headers) 193 - case request.AssertionTextBody: 194 - var target assertions.StringTargetType 195 - if err := json.Unmarshal(a, &target); err != nil { 196 - return fmt.Errorf("unable to unmarshal IntTarget: %w", err) 197 - } 198 - 199 - isSuccessfull = isSuccessfull && target.StringEvaluate(data.Body) 200 - case request.AssertionStatus: 201 - var target assertions.StatusTarget 202 - if err := json.Unmarshal(a, &target); err != nil { 203 - return fmt.Errorf("unable to unmarshal IntTarget: %w", err) 204 - } 205 - 206 - isSuccessfull = isSuccessfull && target.StatusEvaluate(int64(res.Status)) 207 - case request.AssertionJsonBody: 208 - fmt.Println("assertion type", assert.AssertionType) 209 - default: 210 - fmt.Println("! Not Handled assertion type", assert.AssertionType) 211 - } 212 - } 213 - } else { 214 - isSuccessfull = statusCode.IsSuccessful() 173 + isSuccessfull, err = EvaluateHTTPAssertions(req.RawAssertions, data, res) 174 + if err != nil { 175 + return err 215 176 } 216 177 217 178 // let's retry at least once if the status code is not successful. ··· 354 315 355 316 c.JSON(http.StatusOK, nil) 356 317 } 318 + 319 + func EvaluateHTTPAssertions(raw []json.RawMessage, data PingData, res checker.Response) (bool, error) { 320 + statusCode := statusCode(res.Status) 321 + if len(raw) == 0 { 322 + return statusCode.IsSuccessful(), nil 323 + } 324 + isSuccessful := true 325 + for _, a := range raw { 326 + var assert request.Assertion 327 + if err := json.Unmarshal(a, &assert); err != nil { 328 + return false, fmt.Errorf("unable to unmarshal assertion: %w", err) 329 + } 330 + switch assert.AssertionType { 331 + case request.AssertionHeader: 332 + var target assertions.HeaderTarget 333 + if err := json.Unmarshal(a, &target); err != nil { 334 + return false, fmt.Errorf("unable to unmarshal HeaderTarget: %w", err) 335 + } 336 + isSuccessful = isSuccessful && target.HeaderEvaluate(data.Headers) 337 + case request.AssertionTextBody: 338 + var target assertions.StringTargetType 339 + if err := json.Unmarshal(a, &target); err != nil { 340 + return false, fmt.Errorf("unable to unmarshal StringTargetType: %w", err) 341 + } 342 + isSuccessful = isSuccessful && target.StringEvaluate(data.Body) 343 + case request.AssertionStatus: 344 + var target assertions.StatusTarget 345 + if err := json.Unmarshal(a, &target); err != nil { 346 + return false, fmt.Errorf("unable to unmarshal StatusTarget: %w", err) 347 + } 348 + isSuccessful = isSuccessful && target.StatusEvaluate(int64(res.Status)) 349 + case request.AssertionJsonBody: 350 + // TODO: Implement JSON body assertion 351 + default: 352 + fmt.Println("unknown assertion type: ", assert.AssertionType) 353 + // TODO: Handle unknown assertion type 354 + } 355 + } 356 + return isSuccessful, nil 357 + }
+158
apps/checker/handlers/checker_test.go
··· 10 10 "testing" 11 11 12 12 "github.com/gin-gonic/gin" 13 + "github.com/openstatushq/openstatus/apps/checker/checker" 13 14 "github.com/openstatushq/openstatus/apps/checker/handlers" 15 + 14 16 "github.com/openstatushq/openstatus/apps/checker/pkg/tinybird" 15 17 "github.com/openstatushq/openstatus/apps/checker/request" 16 18 "github.com/stretchr/testify/assert" ··· 106 108 fmt.Println(w.Body.String()) 107 109 }) 108 110 } 111 + 112 + 113 + func TestEvaluateAssertions_raw(t *testing.T) { 114 + // Helper to marshal assertion 115 + marshal := func(a any) json.RawMessage { 116 + b, _ := json.Marshal(a) 117 + return b 118 + } 119 + 120 + // Success if no assertions and status code is 200 121 + t.Run("no assertions, status code 200", func(t *testing.T) { 122 + raw := []json.RawMessage{} 123 + data := handlers.PingData{} 124 + res := checker.Response{Status: 200} 125 + ok, err := handlers.EvaluateHTTPAssertions(raw, data, res) 126 + assert.True(t, ok) 127 + assert.NoError(t, err) 128 + }) 129 + 130 + // Header assertion success 131 + t.Run("header assertion success", func(t *testing.T) { 132 + assertion := request.Assertion{AssertionType: request.AssertionHeader} 133 + target := struct { 134 + request.Assertion 135 + Comparator request.StringComparator `json:"compare"` 136 + Key string `json:"key"` 137 + Target string `json:"target"` 138 + }{ 139 + assertion, 140 + request.StringContains, 141 + "X-Test", 142 + "ok", 143 + } 144 + rawMsg := marshal(target) 145 + raw := []json.RawMessage{rawMsg} 146 + data := handlers.PingData{Headers: `{"X-Test":"ok-value"}`} 147 + res := checker.Response{Status: 200} 148 + 149 + ok, err := handlers.EvaluateHTTPAssertions(raw, data, res) 150 + assert.True(t, ok) 151 + assert.NoError(t, err) 152 + }) 153 + 154 + t.Run("header assertion failed", func(t *testing.T) { 155 + assertion := request.Assertion{AssertionType: request.AssertionHeader} 156 + target := struct { 157 + request.Assertion 158 + Comparator request.StringComparator `json:"compare"` 159 + Key string `json:"key"` 160 + Target string `json:"target"` 161 + }{ 162 + assertion, 163 + request.StringContains, 164 + "X-Test", 165 + "not-ok", 166 + } 167 + rawMsg := marshal(target) 168 + raw := []json.RawMessage{rawMsg} 169 + data := handlers.PingData{Headers: `{"X-Test":"ok-value"}`} 170 + res := checker.Response{Status: 200} 171 + 172 + ok, err := handlers.EvaluateHTTPAssertions(raw, data, res) 173 + assert.False(t, ok) 174 + assert.NoError(t, err) 175 + }) 176 + 177 + // Text body assertion failure 178 + t.Run("text body assertion failure", func(t *testing.T) { 179 + assertion := request.Assertion{AssertionType: request.AssertionTextBody} 180 + target := struct { 181 + request.Assertion 182 + Comparator request.StringComparator `json:"compare"` 183 + Target string `json:"target"` 184 + }{ 185 + assertion, 186 + request.StringEquals, 187 + "fail", 188 + } 189 + rawMsg := marshal(target) 190 + raw := []json.RawMessage{rawMsg} 191 + data := handlers.PingData{Body: "ok"} 192 + res := checker.Response{Status: 200} 193 + 194 + 195 + ok, err := handlers.EvaluateHTTPAssertions(raw, data, res) 196 + assert.False(t, ok) 197 + assert.NoError(t, err) 198 + }) 199 + 200 + // Text body assertion failure 201 + t.Run("text body assertion success", func(t *testing.T) { 202 + assertion := request.Assertion{AssertionType: request.AssertionTextBody} 203 + target := struct { 204 + request.Assertion 205 + Comparator request.StringComparator `json:"compare"` 206 + Target string `json:"target"` 207 + }{ 208 + assertion, 209 + request.StringEquals, 210 + "success", 211 + } 212 + rawMsg := marshal(target) 213 + raw := []json.RawMessage{rawMsg} 214 + data := handlers.PingData{Body: "success"} 215 + res := checker.Response{Status: 200} 216 + 217 + 218 + ok, err := handlers.EvaluateHTTPAssertions(raw, data, res) 219 + assert.True(t, ok) 220 + assert.NoError(t, err) 221 + }) 222 + // Status assertion success 223 + t.Run("status assertion success", func(t *testing.T) { 224 + assertion := request.Assertion{AssertionType: request.AssertionStatus} 225 + target := struct { 226 + request.Assertion 227 + Comparator request.NumberComparator `json:"compare"` 228 + Target int64 `json:"target"` 229 + }{ 230 + assertion, 231 + request.NumberEquals, 232 + 200, 233 + } 234 + rawMsg := marshal(target) 235 + raw := []json.RawMessage{rawMsg} 236 + data := handlers.PingData{} 237 + res := checker.Response{Status: 200} 238 + 239 + 240 + ok, err := handlers.EvaluateHTTPAssertions(raw, data, res) 241 + assert.True(t, ok) 242 + assert.NoError(t, err) 243 + }) 244 + 245 + // Malformed assertion 246 + t.Run("malformed assertion", func(t *testing.T) { 247 + raw := []json.RawMessage{[]byte(`{not valid json}`)} 248 + data := handlers.PingData{} 249 + res := checker.Response{Status: 200} 250 + ok, err := handlers.EvaluateHTTPAssertions(raw, data, res) 251 + assert.False(t, ok) 252 + assert.Error(t, err) 253 + }) 254 + 255 + // Unknown assertion type 256 + t.Run("unknown assertion type", func(t *testing.T) { 257 + assertion := request.Assertion{AssertionType: "unknown"} 258 + rawMsg := marshal(assertion) 259 + raw := []json.RawMessage{rawMsg} 260 + data := handlers.PingData{} 261 + res := checker.Response{Status: 200} 262 + ok, err := handlers.EvaluateHTTPAssertions(raw, data, res) 263 + assert.True(t, ok) // Should not fail, just skip 264 + assert.NoError(t, err) 265 + }) 266 + }
+384
apps/checker/handlers/dns.go
··· 1 + package handlers 2 + 3 + import ( 4 + "encoding/json" 5 + "fmt" 6 + "net/http" 7 + "strconv" 8 + "time" 9 + 10 + "github.com/gin-gonic/gin" 11 + "github.com/google/uuid" 12 + "github.com/openstatushq/openstatus/apps/checker/checker" 13 + "github.com/openstatushq/openstatus/apps/checker/pkg/assertions" 14 + "github.com/openstatushq/openstatus/apps/checker/request" 15 + "github.com/rs/zerolog/log" 16 + 17 + "github.com/cenkalti/backoff/v5" 18 + ) 19 + 20 + type DNSResponse struct { 21 + ID string `json:"id"` 22 + ErrorMessage string `json:"errorMessage"` 23 + Region string `json:"region"` 24 + Trigger string `json:"trigger"` 25 + URI string `json:"uri"` 26 + RequestStatus string `json:"requestStatus,omitempty"` 27 + Assertions string `json:"assertions"` 28 + 29 + Records map[string][]string `json:"records"` 30 + 31 + RequestId int64 `json:"requestId,omitempty"` 32 + WorkspaceID int64 `json:"workspaceId"` 33 + MonitorID int64 `json:"monitorId"` 34 + Timestamp int64 `json:"timestamp"` 35 + Latency int64 `json:"latency"` 36 + CronTimestamp int64 `json:"cronTimestamp"` 37 + 38 + Error uint8 `json:"error"` 39 + } 40 + 41 + func (h Handler) DNSHandler(c *gin.Context) { 42 + ctx := c.Request.Context() 43 + const defaultRetry = 3 44 + dataSourceName := "dns_response__v0" 45 + 46 + // Authorization check 47 + if c.GetHeader("Authorization") != fmt.Sprintf("Basic %s", h.Secret) { 48 + c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) 49 + return 50 + } 51 + 52 + // Fly region forwarding 53 + if h.CloudProvider == "fly" { 54 + region := c.GetHeader("fly-prefer-region") 55 + if region != "" && region != h.Region { 56 + c.Header("fly-replay", fmt.Sprintf("region=%s", region)) 57 + c.String(http.StatusAccepted, "Forwarding request to %s", region) 58 + return 59 + } 60 + } 61 + 62 + // Parse request 63 + var req request.DNSCheckerRequest 64 + if err := c.ShouldBindJSON(&req); err != nil { 65 + log.Ctx(ctx).Error().Err(err).Msg("failed to decode checker request") 66 + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) 67 + return 68 + } 69 + 70 + workspaceId, err := strconv.ParseInt(req.WorkspaceID, 10, 64) 71 + if err != nil { 72 + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid workspace id"}) 73 + return 74 + } 75 + 76 + monitorId, err := strconv.ParseInt(req.MonitorID, 10, 64) 77 + if err != nil { 78 + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid monitor id"}) 79 + return 80 + } 81 + 82 + trigger := req.Trigger 83 + if trigger == "" { 84 + trigger = "cron" 85 + } 86 + 87 + retry := defaultRetry 88 + if req.Retry != 0 { 89 + retry = int(req.Retry) 90 + } 91 + 92 + id, e := uuid.NewV7() 93 + if e != nil { 94 + log.Ctx(ctx).Error().Err(e).Msg("failed to generate UUID") 95 + return 96 + } 97 + 98 + statusMap := map[string]string{ 99 + "active": "success", 100 + "error": "error", 101 + "degraded": "degraded", 102 + } 103 + requestStatus := statusMap[req.Status] 104 + 105 + data := DNSResponse{ 106 + ID: id.String(), 107 + Region: h.Region, 108 + Trigger: trigger, 109 + URI: req.URI, 110 + WorkspaceID: workspaceId, 111 + MonitorID: monitorId, 112 + CronTimestamp: req.CronTimestamp, 113 + RequestStatus: requestStatus, 114 + Timestamp: time.Now().UTC().UnixMilli(), 115 + } 116 + 117 + var ( 118 + latency int64 119 + isSuccessful = true 120 + called int 121 + ) 122 + 123 + op := func() (*checker.DnsResponse, error) { 124 + called++ 125 + log.Ctx(ctx).Debug().Msgf("performing dns check for %s (attempt %d/%d)", req.URI, called, retry) 126 + start := time.Now().UTC().UnixMilli() 127 + response, err := checker.Dns(ctx, req.URI) 128 + latency = time.Now().UTC().UnixMilli() - start 129 + 130 + if err != nil { 131 + log.Ctx(ctx).Error().Err(err).Msg("dns check failed") 132 + return nil, err 133 + } 134 + if len(req.RawAssertions) > 0 { 135 + log.Ctx(ctx).Debug().Msgf("evaluating %d dns assertions", len(req.RawAssertions)) 136 + isSuccessful, err = EvaluateDNSAssertions(req.RawAssertions, response) 137 + if err != nil { 138 + return nil, backoff.Permanent(err) 139 + } 140 + } 141 + if !isSuccessful && called < retry { 142 + return nil, backoff.RetryAfter(1) 143 + } 144 + if !isSuccessful { 145 + log.Ctx(ctx).Debug().Msg("dns assertions failed") 146 + return response, backoff.Permanent(fmt.Errorf("assertion failed")) 147 + } 148 + return response, nil 149 + } 150 + 151 + result, err := backoff.Retry(ctx, op, backoff.WithBackOff(backoff.NewExponentialBackOff()), backoff.WithMaxTries(uint(retry))) 152 + data.Latency = latency 153 + data.Records = FormatDNSResult(result) 154 + 155 + if len(req.RawAssertions) > 0 { 156 + if j, err := json.Marshal(req.RawAssertions); err == nil { 157 + data.Assertions = string(j) 158 + } else { 159 + log.Ctx(ctx).Error().Err(err).Msg("failed to marshal assertions") 160 + } 161 + } 162 + 163 + // Status update logic 164 + switch { 165 + case !isSuccessful && req.Status != "error": 166 + log.Ctx(ctx).Debug().Msg("DNS check failed assertions") 167 + checker.UpdateStatus(ctx, checker.UpdateData{ 168 + MonitorId: req.MonitorID, 169 + Status: "error", 170 + Region: h.Region, 171 + Message: err.Error(), 172 + CronTimestamp: req.CronTimestamp, 173 + Latency: latency, 174 + }) 175 + data.RequestStatus = "error" 176 + data.Error = 1 177 + data.ErrorMessage = err.Error() 178 + case isSuccessful && req.DegradedAfter > 0 && latency > req.DegradedAfter && req.Status != "degraded": 179 + checker.UpdateStatus(ctx, checker.UpdateData{ 180 + MonitorId: req.MonitorID, 181 + Status: "degraded", 182 + Region: h.Region, 183 + CronTimestamp: req.CronTimestamp, 184 + Latency: latency, 185 + }) 186 + data.RequestStatus = "degraded" 187 + case isSuccessful && ((req.DegradedAfter == 0 && req.Status != "active") || (latency < req.DegradedAfter && req.DegradedAfter != 0 && req.Status != "active")): 188 + checker.UpdateStatus(ctx, checker.UpdateData{ 189 + MonitorId: req.MonitorID, 190 + Status: "active", 191 + Region: h.Region, 192 + CronTimestamp: req.CronTimestamp, 193 + Latency: latency, 194 + }) 195 + data.RequestStatus = "success" 196 + } 197 + 198 + if err := h.TbClient.SendEvent(ctx, data, dataSourceName); err != nil { 199 + log.Ctx(ctx).Error().Err(err).Msg("failed to send event to tinybird") 200 + } 201 + 202 + c.JSON(http.StatusOK, data) 203 + } 204 + 205 + func (h Handler) DNSHandlerRegion(c *gin.Context) { 206 + ctx := c.Request.Context() 207 + dataSourceName := "check_dns_response__v0" 208 + const defaultRetry = 3 209 + 210 + // Authorization check 211 + if c.GetHeader("Authorization") != fmt.Sprintf("Basic %s", h.Secret) { 212 + c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) 213 + return 214 + } 215 + 216 + // Fly region forwarding 217 + if h.CloudProvider == "fly" { 218 + region := c.GetHeader("fly-prefer-region") 219 + if region != "" && region != h.Region { 220 + c.Header("fly-replay", fmt.Sprintf("region=%s", region)) 221 + c.String(http.StatusAccepted, "Forwarding request to %s", region) 222 + return 223 + } 224 + } 225 + 226 + // Parse request 227 + var req request.DNSCheckerRequest 228 + if err := c.ShouldBindJSON(&req); err != nil { 229 + log.Ctx(ctx).Error().Err(err).Msg("failed to decode checker request") 230 + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) 231 + return 232 + } 233 + 234 + workspaceId, err := strconv.ParseInt(req.WorkspaceID, 10, 64) 235 + if err != nil { 236 + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid workspace id"}) 237 + return 238 + } 239 + 240 + retry := defaultRetry 241 + if req.Retry != 0 { 242 + retry = int(req.Retry) 243 + } 244 + 245 + id, e := uuid.NewV7() 246 + if e != nil { 247 + log.Ctx(ctx).Error().Err(e).Msg("failed to generate UUID") 248 + return 249 + } 250 + 251 + statusMap := map[string]string{ 252 + "active": "success", 253 + "error": "error", 254 + "degraded": "degraded", 255 + } 256 + requestStatus := statusMap[req.Status] 257 + 258 + data := DNSResponse{ 259 + ID: id.String(), 260 + Region: h.Region, 261 + URI: req.URI, 262 + WorkspaceID: workspaceId, 263 + CronTimestamp: req.CronTimestamp, 264 + RequestStatus: requestStatus, 265 + Timestamp: time.Now().UTC().UnixMilli(), 266 + } 267 + 268 + var ( 269 + latency int64 270 + isSuccessful = true 271 + called int 272 + ) 273 + 274 + op := func() (*checker.DnsResponse, error) { 275 + called++ 276 + log.Ctx(ctx).Debug().Msgf("performing dns check for %s (attempt %d/%d)", req.URI, called, retry) 277 + start := time.Now().UTC().UnixMilli() 278 + response, err := checker.Dns(ctx, req.URI) 279 + latency = time.Now().UTC().UnixMilli() - start 280 + 281 + if err != nil { 282 + log.Ctx(ctx).Error().Err(err).Msg("dns check failed") 283 + return nil, err 284 + } 285 + if len(req.RawAssertions) > 0 { 286 + log.Ctx(ctx).Debug().Msgf("evaluating %d dns assertions", len(req.RawAssertions)) 287 + isSuccessful, err = EvaluateDNSAssertions(req.RawAssertions, response) 288 + if err != nil { 289 + return nil, backoff.Permanent(err) 290 + } 291 + } 292 + if !isSuccessful && called < retry { 293 + return nil, backoff.RetryAfter(1) 294 + } 295 + if !isSuccessful { 296 + log.Ctx(ctx).Debug().Msg("dns assertions failed") 297 + return response, backoff.Permanent(fmt.Errorf("assertion failed")) 298 + } 299 + return response, nil 300 + } 301 + 302 + result, err := backoff.Retry(ctx, op, backoff.WithBackOff(backoff.NewExponentialBackOff()), backoff.WithMaxTries(uint(retry))) 303 + data.Latency = latency 304 + data.Records = FormatDNSResult(result) 305 + 306 + if len(req.RawAssertions) > 0 { 307 + if j, err := json.Marshal(req.RawAssertions); err == nil { 308 + data.Assertions = string(j) 309 + } else { 310 + log.Ctx(ctx).Error().Err(err).Msg("failed to marshal assertions") 311 + } 312 + } 313 + 314 + if req.RequestId != 0 { 315 + if err := h.TbClient.SendEvent(ctx, data, dataSourceName); err != nil { 316 + log.Ctx(ctx).Error().Err(err).Msg("failed to send event to tinybird") 317 + } 318 + } 319 + c.JSON(http.StatusOK, data) 320 + 321 + } 322 + 323 + func FormatDNSResult(result *checker.DnsResponse) map[string][]string { 324 + r := make(map[string][]string) 325 + a := make([]string, 0) 326 + aaaa := make([]string, 0) 327 + mx := make([]string, 0) 328 + ns := make([]string, 0) 329 + txt := make([]string, 0) 330 + for _, v := range result.A { 331 + a = append(a, v) 332 + } 333 + r["A"] = a 334 + 335 + for _, v := range result.AAAA { 336 + aaaa = append(aaaa, v) 337 + } 338 + r["AAAA"] = aaaa 339 + 340 + r["CNAME"] = []string{result.CNAME} 341 + for _, v := range result.MX { 342 + mx = append(mx, v) 343 + } 344 + r["MX"] = mx 345 + for _, v := range result.NS { 346 + ns = append(ns, v) 347 + } 348 + r["NS"] = ns 349 + for _, v := range result.TXT { 350 + txt = append(txt, v) 351 + } 352 + r["TXT"] = txt 353 + return r 354 + } 355 + 356 + func EvaluateDNSAssertions(rawAssertions []json.RawMessage, response *checker.DnsResponse) (bool, error) { 357 + for _, a := range rawAssertions { 358 + var assert assertions.RecordTarget 359 + if err := json.Unmarshal(a, &assert); err != nil { 360 + return false, fmt.Errorf("unable to parse assertion: %w", err) 361 + } 362 + var isSuccessfull bool 363 + switch assert.Record { 364 + case request.RecordA: 365 + isSuccessfull = assert.RecordEvaluate(response.A) 366 + case request.RecordAAAA: 367 + isSuccessfull = assert.RecordEvaluate(response.AAAA) 368 + case request.RecordCNAME: 369 + isSuccessfull = assert.RecordEvaluate([]string{response.CNAME}) 370 + case request.RecordMX: 371 + isSuccessfull = assert.RecordEvaluate(response.MX) 372 + case request.RecordNS: 373 + isSuccessfull = assert.RecordEvaluate(response.NS) 374 + case request.RecordTXT: 375 + isSuccessfull = assert.RecordEvaluate(response.TXT) 376 + default: 377 + return false, fmt.Errorf("unknown record type in assertion: %s", assert.Record) 378 + } 379 + if !isSuccessfull { 380 + return false, nil 381 + } 382 + } 383 + return true, nil 384 + }
+181
apps/checker/handlers/dns_test.go
··· 1 + package handlers_test 2 + 3 + import ( 4 + "encoding/json" 5 + "reflect" 6 + "testing" 7 + 8 + // Adjust the import path if necessary to point to the correct package 9 + "github.com/openstatushq/openstatus/apps/checker/checker" 10 + "github.com/openstatushq/openstatus/apps/checker/handlers" 11 + ) 12 + 13 + // Mock DNSResult struct to match the expected input for FormatDNSResult. 14 + // If the real struct is in another package, import it accordingly. 15 + type DNSResult struct { 16 + A []string 17 + AAAA []string 18 + CNAME string 19 + MX []string 20 + NS []string 21 + TXT []string 22 + } 23 + 24 + 25 + 26 + func TestFormatDNSResult(t *testing.T) { 27 + tests := []struct { 28 + name string 29 + input DNSResult 30 + expected map[string][]string 31 + }{ 32 + { 33 + name: "All fields populated", 34 + input: DNSResult{ 35 + A: []string{"1.2.3.4", "5.6.7.8"}, 36 + AAAA: []string{"::1", "2001:db8::1"}, 37 + CNAME: "example.com", 38 + MX: []string{"mx1.example.com", "mx2.example.com"}, 39 + NS: []string{"ns1.example.com", "ns2.example.com"}, 40 + TXT: []string{"v=spf1", "google-site-verification=abc"}, 41 + }, 42 + expected: map[string][]string{ 43 + "A": {"1.2.3.4", "5.6.7.8"}, 44 + "AAAA": {"::1", "2001:db8::1"}, 45 + "CNAME": {"example.com"}, 46 + "MX": {"mx1.example.com", "mx2.example.com"}, 47 + "NS": {"ns1.example.com", "ns2.example.com"}, 48 + "TXT": {"v=spf1", "google-site-verification=abc"}, 49 + }, 50 + }, 51 + { 52 + name: "Empty fields", 53 + input: DNSResult{ 54 + A: []string{}, 55 + AAAA: []string{}, 56 + CNAME: "", 57 + MX: []string{}, 58 + NS: []string{}, 59 + TXT: []string{}, 60 + }, 61 + expected: map[string][]string{ 62 + "A": {}, 63 + "AAAA": {}, 64 + "CNAME": {""}, 65 + "MX": {}, 66 + "NS": {}, 67 + "TXT": {}, 68 + }, 69 + }, 70 + { 71 + name: "Single values", 72 + input: DNSResult{ 73 + A: []string{"8.8.8.8"}, 74 + AAAA: []string{"fe80::1"}, 75 + CNAME: "single.example.com", 76 + MX: []string{"mx.single.com"}, 77 + NS: []string{"ns.single.com"}, 78 + TXT: []string{"single-txt"}, 79 + }, 80 + expected: map[string][]string{ 81 + "A": {"8.8.8.8"}, 82 + "AAAA": {"fe80::1"}, 83 + "CNAME": {"single.example.com"}, 84 + "MX": {"mx.single.com"}, 85 + "NS": {"ns.single.com"}, 86 + "TXT": {"single-txt"}, 87 + }, 88 + }, 89 + } 90 + 91 + for _, tt := range tests { 92 + t.Run(tt.name, func(t *testing.T) { 93 + got := handlers.FormatDNSResult(&checker.DnsResponse{ 94 + A: tt.input.A, 95 + AAAA: tt.input.AAAA, 96 + CNAME: tt.input.CNAME, 97 + MX: tt.input.MX, 98 + NS: tt.input.NS, 99 + TXT: tt.input.TXT, 100 + }) 101 + if !reflect.DeepEqual(got, tt.expected) { 102 + t.Errorf("FormatDNSResult() = %v, want %v", got, tt.expected) 103 + } 104 + }) 105 + } 106 + } 107 + 108 + 109 + func TestEvaluateDNSAssertions(t *testing.T) { 110 + type args struct { 111 + rawAssertions []json.RawMessage 112 + response *checker.DnsResponse 113 + } 114 + tests := []struct { 115 + name string 116 + args args 117 + wantSuccess bool 118 + wantErr bool 119 + }{ 120 + { 121 + name: "A record matches", 122 + args: args{ 123 + rawAssertions: []json.RawMessage{ 124 + json.RawMessage(`{"record":"A","compare":"eq","target":"1.2.3.4"}`), 125 + }, 126 + response: &checker.DnsResponse{ 127 + A: []string{"1.2.3.4", "5.6.7.8"}, 128 + }, 129 + }, 130 + wantSuccess: true, 131 + wantErr: false, 132 + }, 133 + { 134 + name: "CNAME does not match", 135 + args: args{ 136 + rawAssertions: []json.RawMessage{ 137 + json.RawMessage(`{"record":"CNAME","compare":"eq","target":"not-example.com"}`), 138 + }, 139 + response: &checker.DnsResponse{ 140 + CNAME: "example.com", 141 + }, 142 + }, 143 + wantSuccess: false, 144 + wantErr: false, 145 + }, 146 + { 147 + name: "Unknown record type", 148 + args: args{ 149 + rawAssertions: []json.RawMessage{ 150 + json.RawMessage(`{"record":"FOO","compare":"eq","target":"bar"}`), 151 + }, 152 + response: &checker.DnsResponse{}, 153 + }, 154 + wantSuccess: false, 155 + wantErr: true, 156 + }, 157 + { 158 + name: "Invalid assertion JSON", 159 + args: args{ 160 + rawAssertions: []json.RawMessage{ 161 + json.RawMessage(`not a json`), 162 + }, 163 + response: &checker.DnsResponse{}, 164 + }, 165 + wantSuccess: false, 166 + wantErr: true, 167 + }, 168 + } 169 + 170 + for _, tt := range tests { 171 + t.Run(tt.name, func(t *testing.T) { 172 + got, err := handlers.EvaluateDNSAssertions(tt.args.rawAssertions, tt.args.response) 173 + if (err != nil) != tt.wantErr { 174 + t.Errorf("error = %v, wantErr %v", err, tt.wantErr) 175 + } 176 + if got != tt.wantSuccess { 177 + t.Errorf("EvaluateDNSAssertions() = %v, want %v", got, tt.wantSuccess) 178 + } 179 + }) 180 + } 181 + }
+1 -1
apps/checker/handlers/tcp.go
··· 89 89 var response checker.TCPResponse 90 90 91 91 var retry int 92 - if req.Retry == 0 { 92 + if req.Retry != 0 { 93 93 retry = int(req.Retry) 94 94 } else { 95 95 retry = 3
+38
apps/checker/pkg/assertions/assertions.go
··· 3 3 import ( 4 4 "encoding/json" 5 5 "fmt" 6 + "slices" 6 7 "strings" 7 8 8 9 "github.com/openstatushq/openstatus/apps/checker/request" ··· 30 31 type StringTargetType struct { 31 32 Comparator request.StringComparator `json:"compare"` 32 33 Target string `json:"target"` 34 + } 35 + 36 + type RecordTarget struct { 37 + Comparator request.RecordComparator `json:"compare"` 38 + Target string `json:"target"` 39 + Record request.Record `json:"record"` 33 40 } 34 41 35 42 func (target StringTargetType) StringEvaluate(s string) bool { ··· 111 118 } 112 119 return true 113 120 } 121 + 122 + func (target RecordTarget) RecordEvaluate(s []string) bool { 123 + switch target.Comparator { 124 + case request.RecordEquals: 125 + if !slices.Contains(s, target.Target) { 126 + return false 127 + } 128 + 129 + case request.RecordNotEquals: 130 + if slices.Contains(s, target.Target) { 131 + return false 132 + } 133 + 134 + case request.RecordContains: 135 + for _, record := range s { 136 + if strings.Contains(record, target.Target) { 137 + return true 138 + } 139 + } 140 + return false 141 + case request.RecordNotContains: 142 + for _, record := range s { 143 + if strings.Contains(record, target.Target) { 144 + return false 145 + } 146 + } 147 + return true 148 + } 149 + 150 + return true 151 + }
+82
apps/checker/pkg/assertions/assertions_test.go
··· 118 118 }) 119 119 } 120 120 } 121 + 122 + func TestRecordTarget_RecordEvaluate(t *testing.T) { 123 + type fields struct { 124 + Comparator request.RecordComparator 125 + Target string 126 + } 127 + type args struct { 128 + s []string 129 + } 130 + tests := []struct { 131 + name string 132 + fields fields 133 + args args 134 + want bool 135 + }{ 136 + { 137 + name: "RecordEquals true", 138 + fields: fields{Comparator: request.RecordEquals, Target: "foo"}, 139 + args: args{s: []string{"foo", "bar"}}, 140 + want: true, 141 + }, 142 + { 143 + name: "RecordEquals false", 144 + fields: fields{Comparator: request.RecordEquals, Target: "baz"}, 145 + args: args{s: []string{"foo", "bar"}}, 146 + want: false, 147 + }, 148 + { 149 + name: "RecordNotEquals true", 150 + fields: fields{Comparator: request.RecordNotEquals, Target: "baz"}, 151 + args: args{s: []string{"foo", "bar"}}, 152 + want: true, 153 + }, 154 + { 155 + name: "RecordNotEquals false", 156 + fields: fields{Comparator: request.RecordNotEquals, Target: "foo"}, 157 + args: args{s: []string{"foo", "bar"}}, 158 + want: false, 159 + }, 160 + { 161 + name: "RecordContains true", 162 + fields: fields{Comparator: request.RecordContains, Target: "ba"}, 163 + args: args{s: []string{"foo", "bar"}}, 164 + want: true, 165 + }, 166 + { 167 + name: "RecordContains false", 168 + fields: fields{Comparator: request.RecordContains, Target: "baz"}, 169 + args: args{s: []string{"foo", "bar"}}, 170 + want: false, 171 + }, 172 + { 173 + name: "RecordNotContains true", 174 + fields: fields{Comparator: request.RecordNotContains, Target: "baz"}, 175 + args: args{s: []string{"foo", "bar"}}, 176 + want: true, 177 + }, 178 + { 179 + name: "RecordNotContains false", 180 + fields: fields{Comparator: request.RecordNotContains, Target: "ba"}, 181 + args: args{s: []string{"foo", "bar"}}, 182 + want: false, 183 + }, 184 + { 185 + name: "Empty slice", 186 + fields: fields{Comparator: request.RecordEquals, Target: "foo"}, 187 + args: args{s: []string{}}, 188 + want: false, 189 + }, 190 + } 191 + for _, tt := range tests { 192 + t.Run(tt.name, func(t *testing.T) { 193 + target := RecordTarget{ 194 + Comparator: tt.fields.Comparator, 195 + Target: tt.fields.Target, 196 + } 197 + if got := target.RecordEvaluate(tt.args.s); got != tt.want { 198 + t.Errorf("RecordTarget.RecordEvaluate() = %v, want %v", got, tt.want) 199 + } 200 + }) 201 + } 202 + }
+14
apps/checker/pkg/job/dns_job.go
··· 1 + package job 2 + 3 + import ( 4 + "context" 5 + 6 + v1 "github.com/openstatushq/openstatus/apps/checker/proto/private_location/v1" 7 + ) 8 + 9 + type DNSPrivateRegionData struct {} 10 + 11 + func (jobRunner) DNSJob(ctx context.Context, monitor *v1.DNSMonitor) ( *DNSPrivateRegionData, error) { 12 + 13 + return nil,nil 14 + }
+1
apps/checker/pkg/job/job.go
··· 30 30 type JobRunner interface { 31 31 TCPJob(ctx context.Context, monitor *v1.TCPMonitor) (*TCPPrivateRegionData, error) 32 32 HTTPJob(ctx context.Context, monitor *v1.HTTPMonitor) (*HttpPrivateRegionData, error) 33 + DNSJob(ctx context.Context, monitor *v1.DNSMonitor) (*DNSPrivateRegionData, error) 33 34 } 34 35 35 36 type jobRunner struct{}
+54 -108
apps/checker/pkg/scheduler/scheduler.go
··· 100 100 101 101 } 102 102 103 - // // Stop jobs for monitors that no longer exist 104 - // for id := range mm.HttpMonitors { 105 - // if _, stillExists := currentIDs[id]; !stillExists { 106 - // mm.Scheduler.Del(id) 107 - // mm.mu.Lock() 108 - // delete(mm.HttpMonitors, id) 109 - // mm.mu.Unlock() 110 103 111 - // } 112 - // } 113 104 114 105 // TCP monitors: start jobs for new monitors 115 106 for _, m := range res.Msg.TcpMonitors { ··· 163 154 } 164 155 } 165 156 157 + for _, m := range res.Msg.DnsMonitors { 158 + currentIDs[m.Id] = struct{}{} 159 + _, err := mm.Scheduler.Lookup(m.Id) 160 + if err != nil { 161 + 162 + interval := time.Duration(intervalToSecond(m.Periodicity)) * time.Second 163 + task := tasks.Task{ 164 + Interval: interval, 165 + RunOnce: false, 166 + // StartAfter: time.Now().Add(5 * time.Millisecond), 167 + RunSingleInstance: true, 168 + FuncWithTaskContext: func(ctx tasks.TaskContext) error { 169 + 170 + monitor := m 171 + log.Printf("Starting TCP job for monitor %s (%s)", monitor.Id, monitor.Uri) 172 + _, err := mm.JobRunner.DNSJob(ctx.Context, monitor) 173 + if err != nil { 174 + log.Printf("TCP monitor check failed for %s (%s): %v", monitor.Id, monitor.Uri, err) 175 + } 176 + resp, ingestErr := mm.Client.IngestDNS(ctx.Context, &connect.Request[v1.IngestDNSRequest]{ 177 + Msg: &v1.IngestDNSRequest{ 178 + MonitorId: monitor.Id, 179 + 180 + // Id: data.ID, 181 + // Uri: monitor.Uri, 182 + // Message: data.Message, 183 + // Latency: data.Latency, 184 + // RequestStatus: data.RequestStatus, 185 + // Error: int64(data.Error), 186 + // CronTimestamp: data.CronTimestamp, 187 + // Timestamp: data.Timestamp, 188 + }, 189 + }) 190 + if ingestErr != nil { 191 + log.Printf("Failed to ingest TCP result for %s (%s): %v", monitor.Id, monitor.Uri, ingestErr) 192 + return ingestErr 193 + } 194 + log.Printf("TCP monitor check succeeded for %s (%s), ingest response: %v", monitor.Id, monitor.Uri, resp) 195 + 196 + return nil 197 + }, 198 + } 199 + err := mm.Scheduler.AddWithID(m.Id, &task) 200 + 201 + if err != nil { 202 + log.Printf("Failed to add TCP monitor job for %s (%s): %v", m.Id, m.Uri, err) 203 + continue 204 + } 205 + log.Printf("Started TCP monitoring job for %s (%s)", m.Id, m.Uri) 206 + } 207 + } 208 + 209 + 166 210 for id := range mm.Scheduler.Tasks() { 167 211 if _, stillExists := currentIDs[id]; !stillExists { 168 212 mm.Scheduler.Del(id) 169 213 } 170 214 } 171 - // Stop jobs for TCP monitors that no longer exist 172 - // for id := range mm.TcpMonitors { 173 - // if _, stillExists := currentIDs[id]; !stillExists { 174 - // mm.Scheduler.Del(id) 175 - // mm.mu.Lock() 176 - // delete(mm.TcpMonitors, id) 177 - // mm.mu.Unlock() 178 - // } 179 - // } 215 + 180 216 } 181 217 182 218 func intervalToSecond(interval string) int { ··· 199 235 return 0 200 236 } 201 237 } 202 - 203 - // func (mm *MonitorManager) ScheduleHTTPJob(ctx context.Context, monitor *v1.HTTPMonitor, done chan bool) { 204 - // interval := intervalToSecond(monitor.Periodicity) 205 - // if interval == 0 { 206 - // log.Printf("Invalid interval for monitor %s (%s): %s", monitor.Id, monitor.Url, monitor.Periodicity) 207 - // return 208 - // } 209 - 210 - // ticker := time.NewTicker(time.Duration(1) * time.Second) 211 - // defer ticker.Stop() 212 - 213 - // for { 214 - // select { 215 - // case <-ticker.C: 216 - // log.Printf("Starting job for monitor %s (%s)", monitor.Id, monitor.Url) 217 - // data, err := mm.JobRunner.HTTPJob(ctx, monitor) 218 - // if err != nil { 219 - // log.Printf("Monitor check failed for %s (%s): %v", monitor.Id, monitor.Url, err) 220 - // continue 221 - // } 222 - // resp, ingestErr := mm.Client.IngestHTTP(ctx, &connect.Request[v1.IngestHTTPRequest]{ 223 - // Msg: &v1.IngestHTTPRequest{ 224 - // Id: monitor.Id, 225 - // Url: monitor.Url, 226 - // Message: data.Message, 227 - // Latency: data.Latency, 228 - // Timing: data.Timing, 229 - // Headers: data.Headers, 230 - // Body: data.Body, 231 - // RequestStatus: data.RequestStatus, 232 - // StatusCode: int64(data.StatusCode), 233 - // Error: int64(data.Error), 234 - // CronTimestamp: data.CronTimestamp, 235 - // Timestamp: data.Timestamp, 236 - // }, 237 - // }) 238 - // if ingestErr != nil { 239 - // log.Printf("Failed to ingest HTTP result for %s (%s): %v", monitor.Id, monitor.Url, ingestErr) 240 - // } else { 241 - // log.Printf("Monitor check succeeded for %s (%s), ingest response: %v", monitor.Id, monitor.Url, resp) 242 - // } 243 - // case <-done: 244 - // log.Printf("Shutting down job for monitor %s (%s)", monitor.Id, monitor.Url) 245 - // return 246 - // } 247 - // } 248 - // } 249 - 250 - // func (mm *MonitorManager) ScheduleTCPJob(ctx context.Context, monitor *v1.TCPMonitor, done chan bool) { 251 - // interval := intervalToSecond(monitor.Periodicity) 252 - // if interval == 0 { 253 - // log.Printf("Invalid interval for TCP monitor %s (%s): %s", monitor.Id, monitor.Uri, monitor.Periodicity) 254 - // return 255 - // } 256 - 257 - // ticker := time.NewTicker(time.Duration(1) * time.Second) 258 - // defer ticker.Stop() 259 - 260 - // for { 261 - // select { 262 - // case <-ticker.C: 263 - // log.Printf("Starting TCP job for monitor %s (%s)", monitor.Id, monitor.Uri) 264 - // data, err := mm.JobRunner.TCPJob(ctx, monitor) 265 - // if err != nil { 266 - // log.Printf("TCP monitor check failed for %s (%s): %v", monitor.Id, monitor.Uri, err) 267 - // continue 268 - // } 269 - // resp, ingestErr := mm.Client.IngestTCP(ctx, &connect.Request[v1.IngestTCPRequest]{ 270 - // Msg: &v1.IngestTCPRequest{ 271 - // Id: monitor.Id, 272 - // Uri: monitor.Uri, 273 - // Message: data.Message, 274 - // Latency: data.Latency, 275 - // RequestStatus: data.RequestStatus, 276 - // Error: int64(data.Error), 277 - // CronTimestamp: data.CronTimestamp, 278 - // Timestamp: data.Timestamp, 279 - // }, 280 - // }) 281 - // if ingestErr != nil { 282 - // log.Printf("Failed to ingest TCP result for %s (%s): %v", monitor.Id, monitor.Uri, ingestErr) 283 - // } else { 284 - // log.Printf("TCP monitor check succeeded for %s (%s), ingest response: %v", monitor.Id, monitor.Uri, resp) 285 - // } 286 - // case <-done: 287 - // log.Printf("Shutting down TCP job for monitor %s (%s)", monitor.Id, monitor.Uri) 288 - // return 289 - // } 290 - // } 291 - // }
+11
apps/checker/pkg/scheduler/scheduler_test.go
··· 19 19 type mockJobRunner struct { 20 20 HTTPJobCalled atomic.Bool 21 21 TCPJobCalled atomic.Bool 22 + DNSJobCalled atomic.Bool 22 23 mu sync.Mutex 23 24 } 24 25 ··· 32 33 return &job.TCPPrivateRegionData{}, nil 33 34 } 34 35 36 + func (m *mockJobRunner) DNSJob(ctx context.Context, monitor *v1.DNSMonitor) (*job.DNSPrivateRegionData, error) { 37 + 38 + m.TCPJobCalled.Store(true) 39 + return &job.DNSPrivateRegionData{}, nil 40 + } 35 41 // mockClient implements v1.PrivateLocationServiceClient for testing 36 42 type mockClient struct { 37 43 MonitorsFunc func(ctx context.Context, req *connect.Request[v1.MonitorsRequest]) (*connect.Response[v1.MonitorsResponse], error) 38 44 IngestHTTPFunc func(ctx context.Context, req *connect.Request[v1.IngestHTTPRequest]) (*connect.Response[v1.IngestHTTPResponse], error) 39 45 IngestTCPFunc func(ctx context.Context, req *connect.Request[v1.IngestTCPRequest]) (*connect.Response[v1.IngestTCPResponse], error) 46 + IngestDNSFunc func(ctx context.Context, req *connect.Request[v1.IngestDNSRequest]) (*connect.Response[v1.IngestDNSResponse], error) 40 47 } 41 48 42 49 func (m *mockClient) Monitors(ctx context.Context, req *connect.Request[v1.MonitorsRequest]) (*connect.Response[v1.MonitorsResponse], error) { ··· 47 54 } 48 55 func (m *mockClient) IngestTCP(ctx context.Context, req *connect.Request[v1.IngestTCPRequest]) (*connect.Response[v1.IngestTCPResponse], error) { 49 56 return m.IngestTCPFunc(ctx, req) 57 + } 58 + func (m *mockClient) IngestDNS(ctx context.Context, req *connect.Request[v1.IngestDNSRequest]) (*connect.Response[v1.IngestDNSResponse], error) { 59 + return m.IngestDNSFunc(ctx, req) 50 60 } 51 61 52 62 func TestMonitorManager_StartAndStopJobs_WithJobRunner(t *testing.T) { ··· 68 78 IngestTCPFunc: func(ctx context.Context, req *connect.Request[v1.IngestTCPRequest]) (*connect.Response[v1.IngestTCPResponse], error) { 69 79 return connect.NewResponse(&v1.IngestTCPResponse{}), nil 70 80 }, 81 + 71 82 } 72 83 jobRunner := &mockJobRunner{} 73 84
+144 -14
apps/checker/proto/private_location/v1/assertions.pb.go
··· 155 155 return file_private_location_v1_assertions_proto_rawDescGZIP(), []int{1} 156 156 } 157 157 158 + type RecordComparator int32 159 + 160 + const ( 161 + RecordComparator_RECORD_COMPARATOR_UNSPECIFIED RecordComparator = 0 162 + RecordComparator_RECORD_COMPARATOR_EQUAL RecordComparator = 1 163 + RecordComparator_RECORD_COMPARATOR_NOT_EQUAL RecordComparator = 2 164 + RecordComparator_RECORD_COMPARATOR_CONTAINS RecordComparator = 3 165 + RecordComparator_RECORD_COMPARATOR_NOT_CONTAINS RecordComparator = 4 166 + ) 167 + 168 + // Enum value maps for RecordComparator. 169 + var ( 170 + RecordComparator_name = map[int32]string{ 171 + 0: "RECORD_COMPARATOR_UNSPECIFIED", 172 + 1: "RECORD_COMPARATOR_EQUAL", 173 + 2: "RECORD_COMPARATOR_NOT_EQUAL", 174 + 3: "RECORD_COMPARATOR_CONTAINS", 175 + 4: "RECORD_COMPARATOR_NOT_CONTAINS", 176 + } 177 + RecordComparator_value = map[string]int32{ 178 + "RECORD_COMPARATOR_UNSPECIFIED": 0, 179 + "RECORD_COMPARATOR_EQUAL": 1, 180 + "RECORD_COMPARATOR_NOT_EQUAL": 2, 181 + "RECORD_COMPARATOR_CONTAINS": 3, 182 + "RECORD_COMPARATOR_NOT_CONTAINS": 4, 183 + } 184 + ) 185 + 186 + func (x RecordComparator) Enum() *RecordComparator { 187 + p := new(RecordComparator) 188 + *p = x 189 + return p 190 + } 191 + 192 + func (x RecordComparator) String() string { 193 + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 194 + } 195 + 196 + func (RecordComparator) Descriptor() protoreflect.EnumDescriptor { 197 + return file_private_location_v1_assertions_proto_enumTypes[2].Descriptor() 198 + } 199 + 200 + func (RecordComparator) Type() protoreflect.EnumType { 201 + return &file_private_location_v1_assertions_proto_enumTypes[2] 202 + } 203 + 204 + func (x RecordComparator) Number() protoreflect.EnumNumber { 205 + return protoreflect.EnumNumber(x) 206 + } 207 + 208 + // Deprecated: Use RecordComparator.Descriptor instead. 209 + func (RecordComparator) EnumDescriptor() ([]byte, []int) { 210 + return file_private_location_v1_assertions_proto_rawDescGZIP(), []int{2} 211 + } 212 + 158 213 type StatusCodeAssertion struct { 159 214 state protoimpl.MessageState `protogen:"open.v1"` 160 215 Target int64 `protobuf:"varint,1,opt,name=target,proto3" json:"target,omitempty"` ··· 319 374 return "" 320 375 } 321 376 377 + type RecordAssertion struct { 378 + state protoimpl.MessageState `protogen:"open.v1"` 379 + Record string `protobuf:"bytes,1,opt,name=record,proto3" json:"record,omitempty"` 380 + Comparator RecordComparator `protobuf:"varint,2,opt,name=comparator,proto3,enum=private_location.v1.RecordComparator" json:"comparator,omitempty"` 381 + Targert string `protobuf:"bytes,3,opt,name=targert,proto3" json:"targert,omitempty"` 382 + unknownFields protoimpl.UnknownFields 383 + sizeCache protoimpl.SizeCache 384 + } 385 + 386 + func (x *RecordAssertion) Reset() { 387 + *x = RecordAssertion{} 388 + mi := &file_private_location_v1_assertions_proto_msgTypes[3] 389 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 390 + ms.StoreMessageInfo(mi) 391 + } 392 + 393 + func (x *RecordAssertion) String() string { 394 + return protoimpl.X.MessageStringOf(x) 395 + } 396 + 397 + func (*RecordAssertion) ProtoMessage() {} 398 + 399 + func (x *RecordAssertion) ProtoReflect() protoreflect.Message { 400 + mi := &file_private_location_v1_assertions_proto_msgTypes[3] 401 + if x != nil { 402 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 403 + if ms.LoadMessageInfo() == nil { 404 + ms.StoreMessageInfo(mi) 405 + } 406 + return ms 407 + } 408 + return mi.MessageOf(x) 409 + } 410 + 411 + // Deprecated: Use RecordAssertion.ProtoReflect.Descriptor instead. 412 + func (*RecordAssertion) Descriptor() ([]byte, []int) { 413 + return file_private_location_v1_assertions_proto_rawDescGZIP(), []int{3} 414 + } 415 + 416 + func (x *RecordAssertion) GetRecord() string { 417 + if x != nil { 418 + return x.Record 419 + } 420 + return "" 421 + } 422 + 423 + func (x *RecordAssertion) GetComparator() RecordComparator { 424 + if x != nil { 425 + return x.Comparator 426 + } 427 + return RecordComparator_RECORD_COMPARATOR_UNSPECIFIED 428 + } 429 + 430 + func (x *RecordAssertion) GetTargert() string { 431 + if x != nil { 432 + return x.Targert 433 + } 434 + return "" 435 + } 436 + 322 437 var File_private_location_v1_assertions_proto protoreflect.FileDescriptor 323 438 324 439 const file_private_location_v1_assertions_proto_rawDesc = "" + ··· 339 454 "\n" + 340 455 "comparator\x18\x02 \x01(\x0e2%.private_location.v1.StringComparatorR\n" + 341 456 "comparator\x12\x10\n" + 342 - "\x03key\x18\x03 \x01(\tR\x03key*\x8f\x02\n" + 457 + "\x03key\x18\x03 \x01(\tR\x03key\"\x8a\x01\n" + 458 + "\x0fRecordAssertion\x12\x16\n" + 459 + "\x06record\x18\x01 \x01(\tR\x06record\x12E\n" + 460 + "\n" + 461 + "comparator\x18\x02 \x01(\x0e2%.private_location.v1.RecordComparatorR\n" + 462 + "comparator\x12\x18\n" + 463 + "\atargert\x18\x03 \x01(\tR\atargert*\x8f\x02\n" + 343 464 "\x10NumberComparator\x12!\n" + 344 465 "\x1dNUMBER_COMPARATOR_UNSPECIFIED\x10\x00\x12\x1b\n" + 345 466 "\x17NUMBER_COMPARATOR_EQUAL\x10\x01\x12\x1f\n" + ··· 360 481 "'STRING_COMPARATOR_GREATER_THAN_OR_EQUAL\x10\b\x12\x1f\n" + 361 482 "\x1bSTRING_COMPARATOR_LESS_THAN\x10\t\x12(\n" + 362 483 "$STRING_COMPARATOR_LESS_THAN_OR_EQUAL\x10\n" + 363 - "BJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" 484 + "*\xb7\x01\n" + 485 + "\x10RecordComparator\x12!\n" + 486 + "\x1dRECORD_COMPARATOR_UNSPECIFIED\x10\x00\x12\x1b\n" + 487 + "\x17RECORD_COMPARATOR_EQUAL\x10\x01\x12\x1f\n" + 488 + "\x1bRECORD_COMPARATOR_NOT_EQUAL\x10\x02\x12\x1e\n" + 489 + "\x1aRECORD_COMPARATOR_CONTAINS\x10\x03\x12\"\n" + 490 + "\x1eRECORD_COMPARATOR_NOT_CONTAINS\x10\x04BJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" 364 491 365 492 var ( 366 493 file_private_location_v1_assertions_proto_rawDescOnce sync.Once ··· 374 501 return file_private_location_v1_assertions_proto_rawDescData 375 502 } 376 503 377 - var file_private_location_v1_assertions_proto_enumTypes = make([]protoimpl.EnumInfo, 2) 378 - var file_private_location_v1_assertions_proto_msgTypes = make([]protoimpl.MessageInfo, 3) 504 + var file_private_location_v1_assertions_proto_enumTypes = make([]protoimpl.EnumInfo, 3) 505 + var file_private_location_v1_assertions_proto_msgTypes = make([]protoimpl.MessageInfo, 4) 379 506 var file_private_location_v1_assertions_proto_goTypes = []any{ 380 507 (NumberComparator)(0), // 0: private_location.v1.NumberComparator 381 508 (StringComparator)(0), // 1: private_location.v1.StringComparator 382 - (*StatusCodeAssertion)(nil), // 2: private_location.v1.StatusCodeAssertion 383 - (*BodyAssertion)(nil), // 3: private_location.v1.BodyAssertion 384 - (*HeaderAssertion)(nil), // 4: private_location.v1.HeaderAssertion 509 + (RecordComparator)(0), // 2: private_location.v1.RecordComparator 510 + (*StatusCodeAssertion)(nil), // 3: private_location.v1.StatusCodeAssertion 511 + (*BodyAssertion)(nil), // 4: private_location.v1.BodyAssertion 512 + (*HeaderAssertion)(nil), // 5: private_location.v1.HeaderAssertion 513 + (*RecordAssertion)(nil), // 6: private_location.v1.RecordAssertion 385 514 } 386 515 var file_private_location_v1_assertions_proto_depIdxs = []int32{ 387 516 0, // 0: private_location.v1.StatusCodeAssertion.comparator:type_name -> private_location.v1.NumberComparator 388 517 1, // 1: private_location.v1.BodyAssertion.comparator:type_name -> private_location.v1.StringComparator 389 518 1, // 2: private_location.v1.HeaderAssertion.comparator:type_name -> private_location.v1.StringComparator 390 - 3, // [3:3] is the sub-list for method output_type 391 - 3, // [3:3] is the sub-list for method input_type 392 - 3, // [3:3] is the sub-list for extension type_name 393 - 3, // [3:3] is the sub-list for extension extendee 394 - 0, // [0:3] is the sub-list for field type_name 519 + 2, // 3: private_location.v1.RecordAssertion.comparator:type_name -> private_location.v1.RecordComparator 520 + 4, // [4:4] is the sub-list for method output_type 521 + 4, // [4:4] is the sub-list for method input_type 522 + 4, // [4:4] is the sub-list for extension type_name 523 + 4, // [4:4] is the sub-list for extension extendee 524 + 0, // [0:4] is the sub-list for field type_name 395 525 } 396 526 397 527 func init() { file_private_location_v1_assertions_proto_init() } ··· 404 534 File: protoimpl.DescBuilder{ 405 535 GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 406 536 RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_assertions_proto_rawDesc), len(file_private_location_v1_assertions_proto_rawDesc)), 407 - NumEnums: 2, 408 - NumMessages: 3, 537 + NumEnums: 3, 538 + NumMessages: 4, 409 539 NumExtensions: 0, 410 540 NumServices: 0, 411 541 },
+183
apps/checker/proto/private_location/v1/dns_monitor.pb.go
··· 1 + // Code generated by protoc-gen-go. DO NOT EDIT. 2 + // versions: 3 + // protoc-gen-go v1.36.10 4 + // protoc (unknown) 5 + // source: private_location/v1/dns_monitor.proto 6 + 7 + package v1 8 + 9 + import ( 10 + protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 + protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 + reflect "reflect" 13 + sync "sync" 14 + unsafe "unsafe" 15 + ) 16 + 17 + const ( 18 + // Verify that this generated code is sufficiently up-to-date. 19 + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 + // Verify that runtime/protoimpl is sufficiently up-to-date. 21 + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 + ) 23 + 24 + type DNSMonitor struct { 25 + state protoimpl.MessageState `protogen:"open.v1"` 26 + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 27 + Uri string `protobuf:"bytes,2,opt,name=uri,proto3" json:"uri,omitempty"` 28 + Timeout int64 `protobuf:"varint,3,opt,name=timeout,proto3" json:"timeout,omitempty"` 29 + DegradedAt *int64 `protobuf:"varint,4,opt,name=degraded_at,json=degradedAt,proto3,oneof" json:"degraded_at,omitempty"` 30 + Periodicity string `protobuf:"bytes,5,opt,name=periodicity,proto3" json:"periodicity,omitempty"` 31 + Retry int64 `protobuf:"varint,6,opt,name=retry,proto3" json:"retry,omitempty"` 32 + RecordAssertions []*RecordAssertion `protobuf:"bytes,13,rep,name=record_assertions,json=recordAssertions,proto3" json:"record_assertions,omitempty"` 33 + unknownFields protoimpl.UnknownFields 34 + sizeCache protoimpl.SizeCache 35 + } 36 + 37 + func (x *DNSMonitor) Reset() { 38 + *x = DNSMonitor{} 39 + mi := &file_private_location_v1_dns_monitor_proto_msgTypes[0] 40 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 41 + ms.StoreMessageInfo(mi) 42 + } 43 + 44 + func (x *DNSMonitor) String() string { 45 + return protoimpl.X.MessageStringOf(x) 46 + } 47 + 48 + func (*DNSMonitor) ProtoMessage() {} 49 + 50 + func (x *DNSMonitor) ProtoReflect() protoreflect.Message { 51 + mi := &file_private_location_v1_dns_monitor_proto_msgTypes[0] 52 + if x != nil { 53 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 54 + if ms.LoadMessageInfo() == nil { 55 + ms.StoreMessageInfo(mi) 56 + } 57 + return ms 58 + } 59 + return mi.MessageOf(x) 60 + } 61 + 62 + // Deprecated: Use DNSMonitor.ProtoReflect.Descriptor instead. 63 + func (*DNSMonitor) Descriptor() ([]byte, []int) { 64 + return file_private_location_v1_dns_monitor_proto_rawDescGZIP(), []int{0} 65 + } 66 + 67 + func (x *DNSMonitor) GetId() string { 68 + if x != nil { 69 + return x.Id 70 + } 71 + return "" 72 + } 73 + 74 + func (x *DNSMonitor) GetUri() string { 75 + if x != nil { 76 + return x.Uri 77 + } 78 + return "" 79 + } 80 + 81 + func (x *DNSMonitor) GetTimeout() int64 { 82 + if x != nil { 83 + return x.Timeout 84 + } 85 + return 0 86 + } 87 + 88 + func (x *DNSMonitor) GetDegradedAt() int64 { 89 + if x != nil && x.DegradedAt != nil { 90 + return *x.DegradedAt 91 + } 92 + return 0 93 + } 94 + 95 + func (x *DNSMonitor) GetPeriodicity() string { 96 + if x != nil { 97 + return x.Periodicity 98 + } 99 + return "" 100 + } 101 + 102 + func (x *DNSMonitor) GetRetry() int64 { 103 + if x != nil { 104 + return x.Retry 105 + } 106 + return 0 107 + } 108 + 109 + func (x *DNSMonitor) GetRecordAssertions() []*RecordAssertion { 110 + if x != nil { 111 + return x.RecordAssertions 112 + } 113 + return nil 114 + } 115 + 116 + var File_private_location_v1_dns_monitor_proto protoreflect.FileDescriptor 117 + 118 + const file_private_location_v1_dns_monitor_proto_rawDesc = "" + 119 + "\n" + 120 + "%private_location/v1/dns_monitor.proto\x12\x13private_location.v1\x1a$private_location/v1/assertions.proto\"\x89\x02\n" + 121 + "\n" + 122 + "DNSMonitor\x12\x0e\n" + 123 + "\x02id\x18\x01 \x01(\tR\x02id\x12\x10\n" + 124 + "\x03uri\x18\x02 \x01(\tR\x03uri\x12\x18\n" + 125 + "\atimeout\x18\x03 \x01(\x03R\atimeout\x12$\n" + 126 + "\vdegraded_at\x18\x04 \x01(\x03H\x00R\n" + 127 + "degradedAt\x88\x01\x01\x12 \n" + 128 + "\vperiodicity\x18\x05 \x01(\tR\vperiodicity\x12\x14\n" + 129 + "\x05retry\x18\x06 \x01(\x03R\x05retry\x12Q\n" + 130 + "\x11record_assertions\x18\r \x03(\v2$.private_location.v1.RecordAssertionR\x10recordAssertionsB\x0e\n" + 131 + "\f_degraded_atBJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" 132 + 133 + var ( 134 + file_private_location_v1_dns_monitor_proto_rawDescOnce sync.Once 135 + file_private_location_v1_dns_monitor_proto_rawDescData []byte 136 + ) 137 + 138 + func file_private_location_v1_dns_monitor_proto_rawDescGZIP() []byte { 139 + file_private_location_v1_dns_monitor_proto_rawDescOnce.Do(func() { 140 + file_private_location_v1_dns_monitor_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_private_location_v1_dns_monitor_proto_rawDesc), len(file_private_location_v1_dns_monitor_proto_rawDesc))) 141 + }) 142 + return file_private_location_v1_dns_monitor_proto_rawDescData 143 + } 144 + 145 + var file_private_location_v1_dns_monitor_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 146 + var file_private_location_v1_dns_monitor_proto_goTypes = []any{ 147 + (*DNSMonitor)(nil), // 0: private_location.v1.DNSMonitor 148 + (*RecordAssertion)(nil), // 1: private_location.v1.RecordAssertion 149 + } 150 + var file_private_location_v1_dns_monitor_proto_depIdxs = []int32{ 151 + 1, // 0: private_location.v1.DNSMonitor.record_assertions:type_name -> private_location.v1.RecordAssertion 152 + 1, // [1:1] is the sub-list for method output_type 153 + 1, // [1:1] is the sub-list for method input_type 154 + 1, // [1:1] is the sub-list for extension type_name 155 + 1, // [1:1] is the sub-list for extension extendee 156 + 0, // [0:1] is the sub-list for field type_name 157 + } 158 + 159 + func init() { file_private_location_v1_dns_monitor_proto_init() } 160 + func file_private_location_v1_dns_monitor_proto_init() { 161 + if File_private_location_v1_dns_monitor_proto != nil { 162 + return 163 + } 164 + file_private_location_v1_assertions_proto_init() 165 + file_private_location_v1_dns_monitor_proto_msgTypes[0].OneofWrappers = []any{} 166 + type x struct{} 167 + out := protoimpl.TypeBuilder{ 168 + File: protoimpl.DescBuilder{ 169 + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 170 + RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_dns_monitor_proto_rawDesc), len(file_private_location_v1_dns_monitor_proto_rawDesc)), 171 + NumEnums: 0, 172 + NumMessages: 1, 173 + NumExtensions: 0, 174 + NumServices: 0, 175 + }, 176 + GoTypes: file_private_location_v1_dns_monitor_proto_goTypes, 177 + DependencyIndexes: file_private_location_v1_dns_monitor_proto_depIdxs, 178 + MessageInfos: file_private_location_v1_dns_monitor_proto_msgTypes, 179 + }.Build() 180 + File_private_location_v1_dns_monitor_proto = out.File 181 + file_private_location_v1_dns_monitor_proto_goTypes = nil 182 + file_private_location_v1_dns_monitor_proto_depIdxs = nil 183 + }
+29
apps/checker/proto/private_location/v1/private_location.connect.go
··· 41 41 // PrivateLocationServiceIngestHTTPProcedure is the fully-qualified name of the 42 42 // PrivateLocationService's IngestHTTP RPC. 43 43 PrivateLocationServiceIngestHTTPProcedure = "/private_location.v1.PrivateLocationService/IngestHTTP" 44 + // PrivateLocationServiceIngestDNSProcedure is the fully-qualified name of the 45 + // PrivateLocationService's IngestDNS RPC. 46 + PrivateLocationServiceIngestDNSProcedure = "/private_location.v1.PrivateLocationService/IngestDNS" 44 47 ) 45 48 46 49 // PrivateLocationServiceClient is a client for the private_location.v1.PrivateLocationService ··· 49 52 Monitors(context.Context, *connect.Request[MonitorsRequest]) (*connect.Response[MonitorsResponse], error) 50 53 IngestTCP(context.Context, *connect.Request[IngestTCPRequest]) (*connect.Response[IngestTCPResponse], error) 51 54 IngestHTTP(context.Context, *connect.Request[IngestHTTPRequest]) (*connect.Response[IngestHTTPResponse], error) 55 + IngestDNS(context.Context, *connect.Request[IngestDNSRequest]) (*connect.Response[IngestDNSResponse], error) 52 56 } 53 57 54 58 // NewPrivateLocationServiceClient constructs a client for the ··· 80 84 connect.WithSchema(privateLocationServiceMethods.ByName("IngestHTTP")), 81 85 connect.WithClientOptions(opts...), 82 86 ), 87 + ingestDNS: connect.NewClient[IngestDNSRequest, IngestDNSResponse]( 88 + httpClient, 89 + baseURL+PrivateLocationServiceIngestDNSProcedure, 90 + connect.WithSchema(privateLocationServiceMethods.ByName("IngestDNS")), 91 + connect.WithClientOptions(opts...), 92 + ), 83 93 } 84 94 } 85 95 ··· 88 98 monitors *connect.Client[MonitorsRequest, MonitorsResponse] 89 99 ingestTCP *connect.Client[IngestTCPRequest, IngestTCPResponse] 90 100 ingestHTTP *connect.Client[IngestHTTPRequest, IngestHTTPResponse] 101 + ingestDNS *connect.Client[IngestDNSRequest, IngestDNSResponse] 91 102 } 92 103 93 104 // Monitors calls private_location.v1.PrivateLocationService.Monitors. ··· 105 116 return c.ingestHTTP.CallUnary(ctx, req) 106 117 } 107 118 119 + // IngestDNS calls private_location.v1.PrivateLocationService.IngestDNS. 120 + func (c *privateLocationServiceClient) IngestDNS(ctx context.Context, req *connect.Request[IngestDNSRequest]) (*connect.Response[IngestDNSResponse], error) { 121 + return c.ingestDNS.CallUnary(ctx, req) 122 + } 123 + 108 124 // PrivateLocationServiceHandler is an implementation of the 109 125 // private_location.v1.PrivateLocationService service. 110 126 type PrivateLocationServiceHandler interface { 111 127 Monitors(context.Context, *connect.Request[MonitorsRequest]) (*connect.Response[MonitorsResponse], error) 112 128 IngestTCP(context.Context, *connect.Request[IngestTCPRequest]) (*connect.Response[IngestTCPResponse], error) 113 129 IngestHTTP(context.Context, *connect.Request[IngestHTTPRequest]) (*connect.Response[IngestHTTPResponse], error) 130 + IngestDNS(context.Context, *connect.Request[IngestDNSRequest]) (*connect.Response[IngestDNSResponse], error) 114 131 } 115 132 116 133 // NewPrivateLocationServiceHandler builds an HTTP handler from the service implementation. It ··· 138 155 connect.WithSchema(privateLocationServiceMethods.ByName("IngestHTTP")), 139 156 connect.WithHandlerOptions(opts...), 140 157 ) 158 + privateLocationServiceIngestDNSHandler := connect.NewUnaryHandler( 159 + PrivateLocationServiceIngestDNSProcedure, 160 + svc.IngestDNS, 161 + connect.WithSchema(privateLocationServiceMethods.ByName("IngestDNS")), 162 + connect.WithHandlerOptions(opts...), 163 + ) 141 164 return "/private_location.v1.PrivateLocationService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 142 165 switch r.URL.Path { 143 166 case PrivateLocationServiceMonitorsProcedure: ··· 146 169 privateLocationServiceIngestTCPHandler.ServeHTTP(w, r) 147 170 case PrivateLocationServiceIngestHTTPProcedure: 148 171 privateLocationServiceIngestHTTPHandler.ServeHTTP(w, r) 172 + case PrivateLocationServiceIngestDNSProcedure: 173 + privateLocationServiceIngestDNSHandler.ServeHTTP(w, r) 149 174 default: 150 175 http.NotFound(w, r) 151 176 } ··· 166 191 func (UnimplementedPrivateLocationServiceHandler) IngestHTTP(context.Context, *connect.Request[IngestHTTPRequest]) (*connect.Response[IngestHTTPResponse], error) { 167 192 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("private_location.v1.PrivateLocationService.IngestHTTP is not implemented")) 168 193 } 194 + 195 + func (UnimplementedPrivateLocationServiceHandler) IngestDNS(context.Context, *connect.Request[IngestDNSRequest]) (*connect.Response[IngestDNSResponse], error) { 196 + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("private_location.v1.PrivateLocationService.IngestDNS is not implemented")) 197 + }
+267 -22
apps/checker/proto/private_location/v1/private_location.pb.go
··· 9 9 import ( 10 10 protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 11 protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 + _ "google.golang.org/protobuf/types/known/structpb" 12 13 reflect "reflect" 13 14 sync "sync" 14 15 unsafe "unsafe" ··· 61 62 state protoimpl.MessageState `protogen:"open.v1"` 62 63 HttpMonitors []*HTTPMonitor `protobuf:"bytes,1,rep,name=http_monitors,json=httpMonitors,proto3" json:"http_monitors,omitempty"` 63 64 TcpMonitors []*TCPMonitor `protobuf:"bytes,2,rep,name=tcp_monitors,json=tcpMonitors,proto3" json:"tcp_monitors,omitempty"` 65 + DnsMonitors []*DNSMonitor `protobuf:"bytes,3,rep,name=dns_monitors,json=dnsMonitors,proto3" json:"dns_monitors,omitempty"` 64 66 unknownFields protoimpl.UnknownFields 65 67 sizeCache protoimpl.SizeCache 66 68 } ··· 105 107 func (x *MonitorsResponse) GetTcpMonitors() []*TCPMonitor { 106 108 if x != nil { 107 109 return x.TcpMonitors 110 + } 111 + return nil 112 + } 113 + 114 + func (x *MonitorsResponse) GetDnsMonitors() []*DNSMonitor { 115 + if x != nil { 116 + return x.DnsMonitors 108 117 } 109 118 return nil 110 119 } ··· 437 446 return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{5} 438 447 } 439 448 449 + type Records struct { 450 + state protoimpl.MessageState `protogen:"open.v1"` 451 + Record []string `protobuf:"bytes,1,rep,name=record,proto3" json:"record,omitempty"` 452 + unknownFields protoimpl.UnknownFields 453 + sizeCache protoimpl.SizeCache 454 + } 455 + 456 + func (x *Records) Reset() { 457 + *x = Records{} 458 + mi := &file_private_location_v1_private_location_proto_msgTypes[6] 459 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 460 + ms.StoreMessageInfo(mi) 461 + } 462 + 463 + func (x *Records) String() string { 464 + return protoimpl.X.MessageStringOf(x) 465 + } 466 + 467 + func (*Records) ProtoMessage() {} 468 + 469 + func (x *Records) ProtoReflect() protoreflect.Message { 470 + mi := &file_private_location_v1_private_location_proto_msgTypes[6] 471 + if x != nil { 472 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 473 + if ms.LoadMessageInfo() == nil { 474 + ms.StoreMessageInfo(mi) 475 + } 476 + return ms 477 + } 478 + return mi.MessageOf(x) 479 + } 480 + 481 + // Deprecated: Use Records.ProtoReflect.Descriptor instead. 482 + func (*Records) Descriptor() ([]byte, []int) { 483 + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{6} 484 + } 485 + 486 + func (x *Records) GetRecord() []string { 487 + if x != nil { 488 + return x.Record 489 + } 490 + return nil 491 + } 492 + 493 + type IngestDNSRequest struct { 494 + state protoimpl.MessageState `protogen:"open.v1"` 495 + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 496 + MonitorId string `protobuf:"bytes,2,opt,name=monitorId,proto3" json:"monitorId,omitempty"` 497 + Latency int64 `protobuf:"varint,3,opt,name=latency,proto3" json:"latency,omitempty"` 498 + Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` 499 + CronTimestamp int64 `protobuf:"varint,5,opt,name=cronTimestamp,proto3" json:"cronTimestamp,omitempty"` 500 + Uri string `protobuf:"bytes,6,opt,name=uri,proto3" json:"uri,omitempty"` 501 + RequestStatus string `protobuf:"bytes,7,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` 502 + Message string `protobuf:"bytes,8,opt,name=message,proto3" json:"message,omitempty"` 503 + Records map[string]*Records `protobuf:"bytes,9,rep,name=records,proto3" json:"records,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` 504 + Timing string `protobuf:"bytes,10,opt,name=timing,proto3" json:"timing,omitempty"` 505 + Error int64 `protobuf:"varint,11,opt,name=error,proto3" json:"error,omitempty"` 506 + unknownFields protoimpl.UnknownFields 507 + sizeCache protoimpl.SizeCache 508 + } 509 + 510 + func (x *IngestDNSRequest) Reset() { 511 + *x = IngestDNSRequest{} 512 + mi := &file_private_location_v1_private_location_proto_msgTypes[7] 513 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 514 + ms.StoreMessageInfo(mi) 515 + } 516 + 517 + func (x *IngestDNSRequest) String() string { 518 + return protoimpl.X.MessageStringOf(x) 519 + } 520 + 521 + func (*IngestDNSRequest) ProtoMessage() {} 522 + 523 + func (x *IngestDNSRequest) ProtoReflect() protoreflect.Message { 524 + mi := &file_private_location_v1_private_location_proto_msgTypes[7] 525 + if x != nil { 526 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 527 + if ms.LoadMessageInfo() == nil { 528 + ms.StoreMessageInfo(mi) 529 + } 530 + return ms 531 + } 532 + return mi.MessageOf(x) 533 + } 534 + 535 + // Deprecated: Use IngestDNSRequest.ProtoReflect.Descriptor instead. 536 + func (*IngestDNSRequest) Descriptor() ([]byte, []int) { 537 + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{7} 538 + } 539 + 540 + func (x *IngestDNSRequest) GetId() string { 541 + if x != nil { 542 + return x.Id 543 + } 544 + return "" 545 + } 546 + 547 + func (x *IngestDNSRequest) GetMonitorId() string { 548 + if x != nil { 549 + return x.MonitorId 550 + } 551 + return "" 552 + } 553 + 554 + func (x *IngestDNSRequest) GetLatency() int64 { 555 + if x != nil { 556 + return x.Latency 557 + } 558 + return 0 559 + } 560 + 561 + func (x *IngestDNSRequest) GetTimestamp() int64 { 562 + if x != nil { 563 + return x.Timestamp 564 + } 565 + return 0 566 + } 567 + 568 + func (x *IngestDNSRequest) GetCronTimestamp() int64 { 569 + if x != nil { 570 + return x.CronTimestamp 571 + } 572 + return 0 573 + } 574 + 575 + func (x *IngestDNSRequest) GetUri() string { 576 + if x != nil { 577 + return x.Uri 578 + } 579 + return "" 580 + } 581 + 582 + func (x *IngestDNSRequest) GetRequestStatus() string { 583 + if x != nil { 584 + return x.RequestStatus 585 + } 586 + return "" 587 + } 588 + 589 + func (x *IngestDNSRequest) GetMessage() string { 590 + if x != nil { 591 + return x.Message 592 + } 593 + return "" 594 + } 595 + 596 + func (x *IngestDNSRequest) GetRecords() map[string]*Records { 597 + if x != nil { 598 + return x.Records 599 + } 600 + return nil 601 + } 602 + 603 + func (x *IngestDNSRequest) GetTiming() string { 604 + if x != nil { 605 + return x.Timing 606 + } 607 + return "" 608 + } 609 + 610 + func (x *IngestDNSRequest) GetError() int64 { 611 + if x != nil { 612 + return x.Error 613 + } 614 + return 0 615 + } 616 + 617 + type IngestDNSResponse struct { 618 + state protoimpl.MessageState `protogen:"open.v1"` 619 + unknownFields protoimpl.UnknownFields 620 + sizeCache protoimpl.SizeCache 621 + } 622 + 623 + func (x *IngestDNSResponse) Reset() { 624 + *x = IngestDNSResponse{} 625 + mi := &file_private_location_v1_private_location_proto_msgTypes[8] 626 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 627 + ms.StoreMessageInfo(mi) 628 + } 629 + 630 + func (x *IngestDNSResponse) String() string { 631 + return protoimpl.X.MessageStringOf(x) 632 + } 633 + 634 + func (*IngestDNSResponse) ProtoMessage() {} 635 + 636 + func (x *IngestDNSResponse) ProtoReflect() protoreflect.Message { 637 + mi := &file_private_location_v1_private_location_proto_msgTypes[8] 638 + if x != nil { 639 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 640 + if ms.LoadMessageInfo() == nil { 641 + ms.StoreMessageInfo(mi) 642 + } 643 + return ms 644 + } 645 + return mi.MessageOf(x) 646 + } 647 + 648 + // Deprecated: Use IngestDNSResponse.ProtoReflect.Descriptor instead. 649 + func (*IngestDNSResponse) Descriptor() ([]byte, []int) { 650 + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{8} 651 + } 652 + 440 653 var File_private_location_v1_private_location_proto protoreflect.FileDescriptor 441 654 442 655 const file_private_location_v1_private_location_proto_rawDesc = "" + 443 656 "\n" + 444 - "*private_location/v1/private_location.proto\x12\x13private_location.v1\x1a&private_location/v1/http_monitor.proto\x1a%private_location/v1/tcp_monitor.proto\"\x11\n" + 445 - "\x0fMonitorsRequest\"\x9d\x01\n" + 657 + "*private_location/v1/private_location.proto\x12\x13private_location.v1\x1a\x1cgoogle/protobuf/struct.proto\x1a%private_location/v1/dns_monitor.proto\x1a&private_location/v1/http_monitor.proto\x1a%private_location/v1/tcp_monitor.proto\"\x11\n" + 658 + "\x0fMonitorsRequest\"\xe1\x01\n" + 446 659 "\x10MonitorsResponse\x12E\n" + 447 660 "\rhttp_monitors\x18\x01 \x03(\v2 .private_location.v1.HTTPMonitorR\fhttpMonitors\x12B\n" + 448 - "\ftcp_monitors\x18\x02 \x03(\v2\x1f.private_location.v1.TCPMonitorR\vtcpMonitors\"\x9e\x02\n" + 661 + "\ftcp_monitors\x18\x02 \x03(\v2\x1f.private_location.v1.TCPMonitorR\vtcpMonitors\x12B\n" + 662 + "\fdns_monitors\x18\x03 \x03(\v2\x1f.private_location.v1.DNSMonitorR\vdnsMonitors\"\x9e\x02\n" + 449 663 "\x10IngestTCPRequest\x12\x0e\n" + 450 664 "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + 451 665 "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + ··· 476 690 "statusCode\x18\f \x01(\x03R\n" + 477 691 "statusCode\x12\x14\n" + 478 692 "\x05error\x18\r \x01(\x03R\x05error\"\x14\n" + 479 - "\x12IngestHTTPResponse2\xb2\x02\n" + 693 + "\x12IngestHTTPResponse\"!\n" + 694 + "\aRecords\x12\x16\n" + 695 + "\x06record\x18\x01 \x03(\tR\x06record\"\xc6\x03\n" + 696 + "\x10IngestDNSRequest\x12\x0e\n" + 697 + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + 698 + "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + 699 + "\alatency\x18\x03 \x01(\x03R\alatency\x12\x1c\n" + 700 + "\ttimestamp\x18\x04 \x01(\x03R\ttimestamp\x12$\n" + 701 + "\rcronTimestamp\x18\x05 \x01(\x03R\rcronTimestamp\x12\x10\n" + 702 + "\x03uri\x18\x06 \x01(\tR\x03uri\x12$\n" + 703 + "\rrequestStatus\x18\a \x01(\tR\rrequestStatus\x12\x18\n" + 704 + "\amessage\x18\b \x01(\tR\amessage\x12L\n" + 705 + "\arecords\x18\t \x03(\v22.private_location.v1.IngestDNSRequest.RecordsEntryR\arecords\x12\x16\n" + 706 + "\x06timing\x18\n" + 707 + " \x01(\tR\x06timing\x12\x14\n" + 708 + "\x05error\x18\v \x01(\x03R\x05error\x1aX\n" + 709 + "\fRecordsEntry\x12\x10\n" + 710 + "\x03key\x18\x01 \x01(\tR\x03key\x122\n" + 711 + "\x05value\x18\x02 \x01(\v2\x1c.private_location.v1.RecordsR\x05value:\x028\x01\"\x13\n" + 712 + "\x11IngestDNSResponse2\x90\x03\n" + 480 713 "\x16PrivateLocationService\x12Y\n" + 481 714 "\bMonitors\x12$.private_location.v1.MonitorsRequest\x1a%.private_location.v1.MonitorsResponse\"\x00\x12\\\n" + 482 715 "\tIngestTCP\x12%.private_location.v1.IngestTCPRequest\x1a&.private_location.v1.IngestTCPResponse\"\x00\x12_\n" + 483 716 "\n" + 484 - "IngestHTTP\x12&.private_location.v1.IngestHTTPRequest\x1a'.private_location.v1.IngestHTTPResponse\"\x00BJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" 717 + "IngestHTTP\x12&.private_location.v1.IngestHTTPRequest\x1a'.private_location.v1.IngestHTTPResponse\"\x00\x12\\\n" + 718 + "\tIngestDNS\x12%.private_location.v1.IngestDNSRequest\x1a&.private_location.v1.IngestDNSResponse\"\x00BJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" 485 719 486 720 var ( 487 721 file_private_location_v1_private_location_proto_rawDescOnce sync.Once ··· 495 729 return file_private_location_v1_private_location_proto_rawDescData 496 730 } 497 731 498 - var file_private_location_v1_private_location_proto_msgTypes = make([]protoimpl.MessageInfo, 6) 732 + var file_private_location_v1_private_location_proto_msgTypes = make([]protoimpl.MessageInfo, 10) 499 733 var file_private_location_v1_private_location_proto_goTypes = []any{ 500 734 (*MonitorsRequest)(nil), // 0: private_location.v1.MonitorsRequest 501 735 (*MonitorsResponse)(nil), // 1: private_location.v1.MonitorsResponse ··· 503 737 (*IngestTCPResponse)(nil), // 3: private_location.v1.IngestTCPResponse 504 738 (*IngestHTTPRequest)(nil), // 4: private_location.v1.IngestHTTPRequest 505 739 (*IngestHTTPResponse)(nil), // 5: private_location.v1.IngestHTTPResponse 506 - (*HTTPMonitor)(nil), // 6: private_location.v1.HTTPMonitor 507 - (*TCPMonitor)(nil), // 7: private_location.v1.TCPMonitor 740 + (*Records)(nil), // 6: private_location.v1.Records 741 + (*IngestDNSRequest)(nil), // 7: private_location.v1.IngestDNSRequest 742 + (*IngestDNSResponse)(nil), // 8: private_location.v1.IngestDNSResponse 743 + nil, // 9: private_location.v1.IngestDNSRequest.RecordsEntry 744 + (*HTTPMonitor)(nil), // 10: private_location.v1.HTTPMonitor 745 + (*TCPMonitor)(nil), // 11: private_location.v1.TCPMonitor 746 + (*DNSMonitor)(nil), // 12: private_location.v1.DNSMonitor 508 747 } 509 748 var file_private_location_v1_private_location_proto_depIdxs = []int32{ 510 - 6, // 0: private_location.v1.MonitorsResponse.http_monitors:type_name -> private_location.v1.HTTPMonitor 511 - 7, // 1: private_location.v1.MonitorsResponse.tcp_monitors:type_name -> private_location.v1.TCPMonitor 512 - 0, // 2: private_location.v1.PrivateLocationService.Monitors:input_type -> private_location.v1.MonitorsRequest 513 - 2, // 3: private_location.v1.PrivateLocationService.IngestTCP:input_type -> private_location.v1.IngestTCPRequest 514 - 4, // 4: private_location.v1.PrivateLocationService.IngestHTTP:input_type -> private_location.v1.IngestHTTPRequest 515 - 1, // 5: private_location.v1.PrivateLocationService.Monitors:output_type -> private_location.v1.MonitorsResponse 516 - 3, // 6: private_location.v1.PrivateLocationService.IngestTCP:output_type -> private_location.v1.IngestTCPResponse 517 - 5, // 7: private_location.v1.PrivateLocationService.IngestHTTP:output_type -> private_location.v1.IngestHTTPResponse 518 - 5, // [5:8] is the sub-list for method output_type 519 - 2, // [2:5] is the sub-list for method input_type 520 - 2, // [2:2] is the sub-list for extension type_name 521 - 2, // [2:2] is the sub-list for extension extendee 522 - 0, // [0:2] is the sub-list for field type_name 749 + 10, // 0: private_location.v1.MonitorsResponse.http_monitors:type_name -> private_location.v1.HTTPMonitor 750 + 11, // 1: private_location.v1.MonitorsResponse.tcp_monitors:type_name -> private_location.v1.TCPMonitor 751 + 12, // 2: private_location.v1.MonitorsResponse.dns_monitors:type_name -> private_location.v1.DNSMonitor 752 + 9, // 3: private_location.v1.IngestDNSRequest.records:type_name -> private_location.v1.IngestDNSRequest.RecordsEntry 753 + 6, // 4: private_location.v1.IngestDNSRequest.RecordsEntry.value:type_name -> private_location.v1.Records 754 + 0, // 5: private_location.v1.PrivateLocationService.Monitors:input_type -> private_location.v1.MonitorsRequest 755 + 2, // 6: private_location.v1.PrivateLocationService.IngestTCP:input_type -> private_location.v1.IngestTCPRequest 756 + 4, // 7: private_location.v1.PrivateLocationService.IngestHTTP:input_type -> private_location.v1.IngestHTTPRequest 757 + 7, // 8: private_location.v1.PrivateLocationService.IngestDNS:input_type -> private_location.v1.IngestDNSRequest 758 + 1, // 9: private_location.v1.PrivateLocationService.Monitors:output_type -> private_location.v1.MonitorsResponse 759 + 3, // 10: private_location.v1.PrivateLocationService.IngestTCP:output_type -> private_location.v1.IngestTCPResponse 760 + 5, // 11: private_location.v1.PrivateLocationService.IngestHTTP:output_type -> private_location.v1.IngestHTTPResponse 761 + 8, // 12: private_location.v1.PrivateLocationService.IngestDNS:output_type -> private_location.v1.IngestDNSResponse 762 + 9, // [9:13] is the sub-list for method output_type 763 + 5, // [5:9] is the sub-list for method input_type 764 + 5, // [5:5] is the sub-list for extension type_name 765 + 5, // [5:5] is the sub-list for extension extendee 766 + 0, // [0:5] is the sub-list for field type_name 523 767 } 524 768 525 769 func init() { file_private_location_v1_private_location_proto_init() } ··· 527 771 if File_private_location_v1_private_location_proto != nil { 528 772 return 529 773 } 774 + file_private_location_v1_dns_monitor_proto_init() 530 775 file_private_location_v1_http_monitor_proto_init() 531 776 file_private_location_v1_tcp_monitor_proto_init() 532 777 type x struct{} ··· 535 780 GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 536 781 RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_private_location_proto_rawDesc), len(file_private_location_v1_private_location_proto_rawDesc)), 537 782 NumEnums: 0, 538 - NumMessages: 6, 783 + NumMessages: 10, 539 784 NumExtensions: 0, 540 785 NumServices: 1, 541 786 },
+43 -4
apps/checker/request/request.go
··· 7 7 type AssertionType string 8 8 9 9 const ( 10 - AssertionHeader AssertionType = "header" 11 - AssertionTextBody AssertionType = "textBody" 12 - AssertionStatus AssertionType = "status" 13 - AssertionJsonBody AssertionType = "jsonBody" 10 + AssertionHeader AssertionType = "header" 11 + AssertionTextBody AssertionType = "textBody" 12 + AssertionStatus AssertionType = "status" 13 + AssertionJsonBody AssertionType = "jsonBody" 14 + AssertionDnsRecord AssertionType = "dnsRecord" 14 15 ) 15 16 16 17 type StringComparator string ··· 39 40 NumberLowerThanEqual NumberComparator = "lte" 40 41 ) 41 42 43 + type RecordComparator string 44 + 45 + const ( 46 + RecordEquals RecordComparator = "eq" 47 + RecordNotEquals RecordComparator = "not_eq" 48 + RecordContains RecordComparator = "contains" 49 + RecordNotContains RecordComparator = "not_contains" 50 + ) 51 + 52 + type Record string 53 + 54 + const ( 55 + RecordA Record = "A" 56 + RecordAAAA Record = "AAAA" 57 + RecordCNAME Record = "CNAME" 58 + RecordMX Record = "MX" 59 + RecordNS Record = "NS" 60 + RecordTXT Record = "TXT" 61 + ) 62 + 42 63 type Assertion struct { 43 64 AssertionType AssertionType `json:"type"` 44 65 Comparator json.RawMessage `json:"compare"` ··· 103 124 RequestId int64 `json:"requestId"` 104 125 WorkspaceId int64 `json:"workspaceId"` 105 126 } 127 + 128 + type DNSCheckerRequest struct { 129 + Status string `json:"status"` 130 + WorkspaceID string `json:"workspaceId"` 131 + URI string `json:"uri"` 132 + MonitorID string `json:"monitorId"` 133 + Trigger string `json:"trigger,omitempty"` 134 + RawAssertions []json.RawMessage `json:"assertions,omitempty"` 135 + RequestId int64 `json:"requestId,omitempty"` 136 + CronTimestamp int64 `json:"cronTimestamp"` 137 + Timeout int64 `json:"timeout"` 138 + DegradedAfter int64 `json:"degradedAfter,omitempty"` 139 + Retry int64 `json:"retry,omitempty"` 140 + OtelConfig struct { 141 + Endpoint string `json:"endpoint"` 142 + Headers map[string]string `json:"headers,omitempty"` 143 + } `json:"otelConfig"` 144 + }
+93
apps/private-location/internal/server/ingest_dns.go
··· 1 + package server 2 + 3 + import ( 4 + "context" 5 + "errors" 6 + "strconv" 7 + "time" 8 + 9 + "connectrpc.com/connect" 10 + "github.com/openstatushq/openstatus/apps/private-location/internal/database" 11 + private_locationv1 "github.com/openstatushq/openstatus/apps/private-location/proto/private_location/v1" 12 + "github.com/rs/zerolog/log" 13 + ) 14 + 15 + type DNSResponse struct { 16 + ID string `json:"id"` 17 + Timing string `json:"timing"` 18 + ErrorMessage string `json:"errorMessage"` 19 + Region string `json:"region"` 20 + Trigger string `json:"trigger"` 21 + URI string `json:"uri"` 22 + RequestStatus string `json:"requestStatus,omitempty"` 23 + Records map[string][]string `json:"records"` 24 + 25 + RequestId int64 `json:"requestId,omitempty"` 26 + WorkspaceID int64 `json:"workspaceId"` 27 + MonitorID int64 `json:"monitorId"` 28 + Timestamp int64 `json:"timestamp"` 29 + Latency int64 `json:"latency"` 30 + CronTimestamp int64 `json:"cronTimestamp"` 31 + 32 + Error uint8 `json:"error"` 33 + } 34 + 35 + func (h *privateLocationHandler) IngestDNS(ctx context.Context, req *connect.Request[private_locationv1.IngestDNSRequest]) (*connect.Response[private_locationv1.IngestDNSResponse], error) { 36 + token := req.Header().Get("openstatus-token") 37 + if token == "" { 38 + return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("missing token")) 39 + } 40 + 41 + dataSourceName := "tcp_dns__v0" 42 + 43 + var monitors database.Monitor 44 + err := h.db.Get(&monitors, "SELECT monitor.* FROM monitor JOIN private_location_to_monitor a ON monitor.id = a.monitor_id JOIN private_location b ON a.private_location_id = b.id WHERE b.token = ? AND monitor.deleted_at IS NULL and monitor.id = ?", token, req.Msg.Id) 45 + 46 + if err != nil { 47 + return nil, connect.NewError(connect.CodeInternal, err) 48 + } 49 + 50 + var region database.PrivateLocation 51 + err = h.db.Get(&region, "SELECT private_location.id FROM private_location join private_location_to_monitor a ON private_location.id = a.private_location_id WHERE a.monitor_id = ? and private_location.token = ?", monitors.ID, token) 52 + 53 + if err != nil { 54 + return nil, connect.NewError(connect.CodeInternal, err) 55 + } 56 + records := make(map[string][]string) 57 + for _, record := range req.Msg.Records { 58 + r := []string{} 59 + for _, value := range record.GetRecord() { 60 + r = append(r, value) 61 + } 62 + records[record.String()] = r 63 + } 64 + 65 + data := DNSResponse{ 66 + ID: req.Msg.Id, 67 + WorkspaceID: int64(monitors.WorkspaceID), 68 + Timestamp: req.Msg.Timestamp, 69 + Error: uint8(req.Msg.Error), 70 + // ErrorMessage: req.Msg.ErrorMessage, 71 + Region: strconv.Itoa(region.ID), 72 + MonitorID: int64(monitors.ID), 73 + Timing: req.Msg.Timing, 74 + Latency: req.Msg.Latency, 75 + CronTimestamp: req.Msg.CronTimestamp, 76 + Trigger: "cron", 77 + URI: req.Msg.Uri, 78 + RequestStatus: req.Msg.RequestStatus, 79 + 80 + Records: records, 81 + } 82 + if err := h.TbClient.SendEvent(ctx, data, dataSourceName); err != nil { 83 + log.Ctx(ctx).Error().Err(err).Msg("failed to send event to tinybird") 84 + } 85 + _, err = h.db.NamedExec("UPDATE private_location SET last_seen_at = :last_seen_at WHERE id = :id", map[string]any{ 86 + "last_seen_at": time.Now().Unix(), 87 + "id": region.ID, 88 + }) 89 + if err != nil { 90 + log.Ctx(ctx).Error().Err(err).Msg("failed to update private location") 91 + } 92 + return connect.NewResponse(&private_locationv1.IngestDNSResponse{}), nil 93 + }
+144 -14
apps/private-location/proto/private_location/v1/assertions.pb.go
··· 155 155 return file_private_location_v1_assertions_proto_rawDescGZIP(), []int{1} 156 156 } 157 157 158 + type RecordComparator int32 159 + 160 + const ( 161 + RecordComparator_RECORD_COMPARATOR_UNSPECIFIED RecordComparator = 0 162 + RecordComparator_RECORD_COMPARATOR_EQUAL RecordComparator = 1 163 + RecordComparator_RECORD_COMPARATOR_NOT_EQUAL RecordComparator = 2 164 + RecordComparator_RECORD_COMPARATOR_CONTAINS RecordComparator = 3 165 + RecordComparator_RECORD_COMPARATOR_NOT_CONTAINS RecordComparator = 4 166 + ) 167 + 168 + // Enum value maps for RecordComparator. 169 + var ( 170 + RecordComparator_name = map[int32]string{ 171 + 0: "RECORD_COMPARATOR_UNSPECIFIED", 172 + 1: "RECORD_COMPARATOR_EQUAL", 173 + 2: "RECORD_COMPARATOR_NOT_EQUAL", 174 + 3: "RECORD_COMPARATOR_CONTAINS", 175 + 4: "RECORD_COMPARATOR_NOT_CONTAINS", 176 + } 177 + RecordComparator_value = map[string]int32{ 178 + "RECORD_COMPARATOR_UNSPECIFIED": 0, 179 + "RECORD_COMPARATOR_EQUAL": 1, 180 + "RECORD_COMPARATOR_NOT_EQUAL": 2, 181 + "RECORD_COMPARATOR_CONTAINS": 3, 182 + "RECORD_COMPARATOR_NOT_CONTAINS": 4, 183 + } 184 + ) 185 + 186 + func (x RecordComparator) Enum() *RecordComparator { 187 + p := new(RecordComparator) 188 + *p = x 189 + return p 190 + } 191 + 192 + func (x RecordComparator) String() string { 193 + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 194 + } 195 + 196 + func (RecordComparator) Descriptor() protoreflect.EnumDescriptor { 197 + return file_private_location_v1_assertions_proto_enumTypes[2].Descriptor() 198 + } 199 + 200 + func (RecordComparator) Type() protoreflect.EnumType { 201 + return &file_private_location_v1_assertions_proto_enumTypes[2] 202 + } 203 + 204 + func (x RecordComparator) Number() protoreflect.EnumNumber { 205 + return protoreflect.EnumNumber(x) 206 + } 207 + 208 + // Deprecated: Use RecordComparator.Descriptor instead. 209 + func (RecordComparator) EnumDescriptor() ([]byte, []int) { 210 + return file_private_location_v1_assertions_proto_rawDescGZIP(), []int{2} 211 + } 212 + 158 213 type StatusCodeAssertion struct { 159 214 state protoimpl.MessageState `protogen:"open.v1"` 160 215 Target int64 `protobuf:"varint,1,opt,name=target,proto3" json:"target,omitempty"` ··· 319 374 return "" 320 375 } 321 376 377 + type RecordAssertion struct { 378 + state protoimpl.MessageState `protogen:"open.v1"` 379 + Record string `protobuf:"bytes,1,opt,name=record,proto3" json:"record,omitempty"` 380 + Comparator RecordComparator `protobuf:"varint,2,opt,name=comparator,proto3,enum=private_location.v1.RecordComparator" json:"comparator,omitempty"` 381 + Targert string `protobuf:"bytes,3,opt,name=targert,proto3" json:"targert,omitempty"` 382 + unknownFields protoimpl.UnknownFields 383 + sizeCache protoimpl.SizeCache 384 + } 385 + 386 + func (x *RecordAssertion) Reset() { 387 + *x = RecordAssertion{} 388 + mi := &file_private_location_v1_assertions_proto_msgTypes[3] 389 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 390 + ms.StoreMessageInfo(mi) 391 + } 392 + 393 + func (x *RecordAssertion) String() string { 394 + return protoimpl.X.MessageStringOf(x) 395 + } 396 + 397 + func (*RecordAssertion) ProtoMessage() {} 398 + 399 + func (x *RecordAssertion) ProtoReflect() protoreflect.Message { 400 + mi := &file_private_location_v1_assertions_proto_msgTypes[3] 401 + if x != nil { 402 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 403 + if ms.LoadMessageInfo() == nil { 404 + ms.StoreMessageInfo(mi) 405 + } 406 + return ms 407 + } 408 + return mi.MessageOf(x) 409 + } 410 + 411 + // Deprecated: Use RecordAssertion.ProtoReflect.Descriptor instead. 412 + func (*RecordAssertion) Descriptor() ([]byte, []int) { 413 + return file_private_location_v1_assertions_proto_rawDescGZIP(), []int{3} 414 + } 415 + 416 + func (x *RecordAssertion) GetRecord() string { 417 + if x != nil { 418 + return x.Record 419 + } 420 + return "" 421 + } 422 + 423 + func (x *RecordAssertion) GetComparator() RecordComparator { 424 + if x != nil { 425 + return x.Comparator 426 + } 427 + return RecordComparator_RECORD_COMPARATOR_UNSPECIFIED 428 + } 429 + 430 + func (x *RecordAssertion) GetTargert() string { 431 + if x != nil { 432 + return x.Targert 433 + } 434 + return "" 435 + } 436 + 322 437 var File_private_location_v1_assertions_proto protoreflect.FileDescriptor 323 438 324 439 const file_private_location_v1_assertions_proto_rawDesc = "" + ··· 339 454 "\n" + 340 455 "comparator\x18\x02 \x01(\x0e2%.private_location.v1.StringComparatorR\n" + 341 456 "comparator\x12\x10\n" + 342 - "\x03key\x18\x03 \x01(\tR\x03key*\x8f\x02\n" + 457 + "\x03key\x18\x03 \x01(\tR\x03key\"\x8a\x01\n" + 458 + "\x0fRecordAssertion\x12\x16\n" + 459 + "\x06record\x18\x01 \x01(\tR\x06record\x12E\n" + 460 + "\n" + 461 + "comparator\x18\x02 \x01(\x0e2%.private_location.v1.RecordComparatorR\n" + 462 + "comparator\x12\x18\n" + 463 + "\atargert\x18\x03 \x01(\tR\atargert*\x8f\x02\n" + 343 464 "\x10NumberComparator\x12!\n" + 344 465 "\x1dNUMBER_COMPARATOR_UNSPECIFIED\x10\x00\x12\x1b\n" + 345 466 "\x17NUMBER_COMPARATOR_EQUAL\x10\x01\x12\x1f\n" + ··· 360 481 "'STRING_COMPARATOR_GREATER_THAN_OR_EQUAL\x10\b\x12\x1f\n" + 361 482 "\x1bSTRING_COMPARATOR_LESS_THAN\x10\t\x12(\n" + 362 483 "$STRING_COMPARATOR_LESS_THAN_OR_EQUAL\x10\n" + 363 - "BJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" 484 + "*\xb7\x01\n" + 485 + "\x10RecordComparator\x12!\n" + 486 + "\x1dRECORD_COMPARATOR_UNSPECIFIED\x10\x00\x12\x1b\n" + 487 + "\x17RECORD_COMPARATOR_EQUAL\x10\x01\x12\x1f\n" + 488 + "\x1bRECORD_COMPARATOR_NOT_EQUAL\x10\x02\x12\x1e\n" + 489 + "\x1aRECORD_COMPARATOR_CONTAINS\x10\x03\x12\"\n" + 490 + "\x1eRECORD_COMPARATOR_NOT_CONTAINS\x10\x04BJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" 364 491 365 492 var ( 366 493 file_private_location_v1_assertions_proto_rawDescOnce sync.Once ··· 374 501 return file_private_location_v1_assertions_proto_rawDescData 375 502 } 376 503 377 - var file_private_location_v1_assertions_proto_enumTypes = make([]protoimpl.EnumInfo, 2) 378 - var file_private_location_v1_assertions_proto_msgTypes = make([]protoimpl.MessageInfo, 3) 504 + var file_private_location_v1_assertions_proto_enumTypes = make([]protoimpl.EnumInfo, 3) 505 + var file_private_location_v1_assertions_proto_msgTypes = make([]protoimpl.MessageInfo, 4) 379 506 var file_private_location_v1_assertions_proto_goTypes = []any{ 380 507 (NumberComparator)(0), // 0: private_location.v1.NumberComparator 381 508 (StringComparator)(0), // 1: private_location.v1.StringComparator 382 - (*StatusCodeAssertion)(nil), // 2: private_location.v1.StatusCodeAssertion 383 - (*BodyAssertion)(nil), // 3: private_location.v1.BodyAssertion 384 - (*HeaderAssertion)(nil), // 4: private_location.v1.HeaderAssertion 509 + (RecordComparator)(0), // 2: private_location.v1.RecordComparator 510 + (*StatusCodeAssertion)(nil), // 3: private_location.v1.StatusCodeAssertion 511 + (*BodyAssertion)(nil), // 4: private_location.v1.BodyAssertion 512 + (*HeaderAssertion)(nil), // 5: private_location.v1.HeaderAssertion 513 + (*RecordAssertion)(nil), // 6: private_location.v1.RecordAssertion 385 514 } 386 515 var file_private_location_v1_assertions_proto_depIdxs = []int32{ 387 516 0, // 0: private_location.v1.StatusCodeAssertion.comparator:type_name -> private_location.v1.NumberComparator 388 517 1, // 1: private_location.v1.BodyAssertion.comparator:type_name -> private_location.v1.StringComparator 389 518 1, // 2: private_location.v1.HeaderAssertion.comparator:type_name -> private_location.v1.StringComparator 390 - 3, // [3:3] is the sub-list for method output_type 391 - 3, // [3:3] is the sub-list for method input_type 392 - 3, // [3:3] is the sub-list for extension type_name 393 - 3, // [3:3] is the sub-list for extension extendee 394 - 0, // [0:3] is the sub-list for field type_name 519 + 2, // 3: private_location.v1.RecordAssertion.comparator:type_name -> private_location.v1.RecordComparator 520 + 4, // [4:4] is the sub-list for method output_type 521 + 4, // [4:4] is the sub-list for method input_type 522 + 4, // [4:4] is the sub-list for extension type_name 523 + 4, // [4:4] is the sub-list for extension extendee 524 + 0, // [0:4] is the sub-list for field type_name 395 525 } 396 526 397 527 func init() { file_private_location_v1_assertions_proto_init() } ··· 404 534 File: protoimpl.DescBuilder{ 405 535 GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 406 536 RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_assertions_proto_rawDesc), len(file_private_location_v1_assertions_proto_rawDesc)), 407 - NumEnums: 2, 408 - NumMessages: 3, 537 + NumEnums: 3, 538 + NumMessages: 4, 409 539 NumExtensions: 0, 410 540 NumServices: 0, 411 541 },
+183
apps/private-location/proto/private_location/v1/dns_monitor.pb.go
··· 1 + // Code generated by protoc-gen-go. DO NOT EDIT. 2 + // versions: 3 + // protoc-gen-go v1.36.10 4 + // protoc (unknown) 5 + // source: private_location/v1/dns_monitor.proto 6 + 7 + package v1 8 + 9 + import ( 10 + protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 + protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 + reflect "reflect" 13 + sync "sync" 14 + unsafe "unsafe" 15 + ) 16 + 17 + const ( 18 + // Verify that this generated code is sufficiently up-to-date. 19 + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 + // Verify that runtime/protoimpl is sufficiently up-to-date. 21 + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 + ) 23 + 24 + type DNSMonitor struct { 25 + state protoimpl.MessageState `protogen:"open.v1"` 26 + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 27 + Uri string `protobuf:"bytes,2,opt,name=uri,proto3" json:"uri,omitempty"` 28 + Timeout int64 `protobuf:"varint,3,opt,name=timeout,proto3" json:"timeout,omitempty"` 29 + DegradedAt *int64 `protobuf:"varint,4,opt,name=degraded_at,json=degradedAt,proto3,oneof" json:"degraded_at,omitempty"` 30 + Periodicity string `protobuf:"bytes,5,opt,name=periodicity,proto3" json:"periodicity,omitempty"` 31 + Retry int64 `protobuf:"varint,6,opt,name=retry,proto3" json:"retry,omitempty"` 32 + RecordAssertions []*RecordAssertion `protobuf:"bytes,13,rep,name=record_assertions,json=recordAssertions,proto3" json:"record_assertions,omitempty"` 33 + unknownFields protoimpl.UnknownFields 34 + sizeCache protoimpl.SizeCache 35 + } 36 + 37 + func (x *DNSMonitor) Reset() { 38 + *x = DNSMonitor{} 39 + mi := &file_private_location_v1_dns_monitor_proto_msgTypes[0] 40 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 41 + ms.StoreMessageInfo(mi) 42 + } 43 + 44 + func (x *DNSMonitor) String() string { 45 + return protoimpl.X.MessageStringOf(x) 46 + } 47 + 48 + func (*DNSMonitor) ProtoMessage() {} 49 + 50 + func (x *DNSMonitor) ProtoReflect() protoreflect.Message { 51 + mi := &file_private_location_v1_dns_monitor_proto_msgTypes[0] 52 + if x != nil { 53 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 54 + if ms.LoadMessageInfo() == nil { 55 + ms.StoreMessageInfo(mi) 56 + } 57 + return ms 58 + } 59 + return mi.MessageOf(x) 60 + } 61 + 62 + // Deprecated: Use DNSMonitor.ProtoReflect.Descriptor instead. 63 + func (*DNSMonitor) Descriptor() ([]byte, []int) { 64 + return file_private_location_v1_dns_monitor_proto_rawDescGZIP(), []int{0} 65 + } 66 + 67 + func (x *DNSMonitor) GetId() string { 68 + if x != nil { 69 + return x.Id 70 + } 71 + return "" 72 + } 73 + 74 + func (x *DNSMonitor) GetUri() string { 75 + if x != nil { 76 + return x.Uri 77 + } 78 + return "" 79 + } 80 + 81 + func (x *DNSMonitor) GetTimeout() int64 { 82 + if x != nil { 83 + return x.Timeout 84 + } 85 + return 0 86 + } 87 + 88 + func (x *DNSMonitor) GetDegradedAt() int64 { 89 + if x != nil && x.DegradedAt != nil { 90 + return *x.DegradedAt 91 + } 92 + return 0 93 + } 94 + 95 + func (x *DNSMonitor) GetPeriodicity() string { 96 + if x != nil { 97 + return x.Periodicity 98 + } 99 + return "" 100 + } 101 + 102 + func (x *DNSMonitor) GetRetry() int64 { 103 + if x != nil { 104 + return x.Retry 105 + } 106 + return 0 107 + } 108 + 109 + func (x *DNSMonitor) GetRecordAssertions() []*RecordAssertion { 110 + if x != nil { 111 + return x.RecordAssertions 112 + } 113 + return nil 114 + } 115 + 116 + var File_private_location_v1_dns_monitor_proto protoreflect.FileDescriptor 117 + 118 + const file_private_location_v1_dns_monitor_proto_rawDesc = "" + 119 + "\n" + 120 + "%private_location/v1/dns_monitor.proto\x12\x13private_location.v1\x1a$private_location/v1/assertions.proto\"\x89\x02\n" + 121 + "\n" + 122 + "DNSMonitor\x12\x0e\n" + 123 + "\x02id\x18\x01 \x01(\tR\x02id\x12\x10\n" + 124 + "\x03uri\x18\x02 \x01(\tR\x03uri\x12\x18\n" + 125 + "\atimeout\x18\x03 \x01(\x03R\atimeout\x12$\n" + 126 + "\vdegraded_at\x18\x04 \x01(\x03H\x00R\n" + 127 + "degradedAt\x88\x01\x01\x12 \n" + 128 + "\vperiodicity\x18\x05 \x01(\tR\vperiodicity\x12\x14\n" + 129 + "\x05retry\x18\x06 \x01(\x03R\x05retry\x12Q\n" + 130 + "\x11record_assertions\x18\r \x03(\v2$.private_location.v1.RecordAssertionR\x10recordAssertionsB\x0e\n" + 131 + "\f_degraded_atBJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" 132 + 133 + var ( 134 + file_private_location_v1_dns_monitor_proto_rawDescOnce sync.Once 135 + file_private_location_v1_dns_monitor_proto_rawDescData []byte 136 + ) 137 + 138 + func file_private_location_v1_dns_monitor_proto_rawDescGZIP() []byte { 139 + file_private_location_v1_dns_monitor_proto_rawDescOnce.Do(func() { 140 + file_private_location_v1_dns_monitor_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_private_location_v1_dns_monitor_proto_rawDesc), len(file_private_location_v1_dns_monitor_proto_rawDesc))) 141 + }) 142 + return file_private_location_v1_dns_monitor_proto_rawDescData 143 + } 144 + 145 + var file_private_location_v1_dns_monitor_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 146 + var file_private_location_v1_dns_monitor_proto_goTypes = []any{ 147 + (*DNSMonitor)(nil), // 0: private_location.v1.DNSMonitor 148 + (*RecordAssertion)(nil), // 1: private_location.v1.RecordAssertion 149 + } 150 + var file_private_location_v1_dns_monitor_proto_depIdxs = []int32{ 151 + 1, // 0: private_location.v1.DNSMonitor.record_assertions:type_name -> private_location.v1.RecordAssertion 152 + 1, // [1:1] is the sub-list for method output_type 153 + 1, // [1:1] is the sub-list for method input_type 154 + 1, // [1:1] is the sub-list for extension type_name 155 + 1, // [1:1] is the sub-list for extension extendee 156 + 0, // [0:1] is the sub-list for field type_name 157 + } 158 + 159 + func init() { file_private_location_v1_dns_monitor_proto_init() } 160 + func file_private_location_v1_dns_monitor_proto_init() { 161 + if File_private_location_v1_dns_monitor_proto != nil { 162 + return 163 + } 164 + file_private_location_v1_assertions_proto_init() 165 + file_private_location_v1_dns_monitor_proto_msgTypes[0].OneofWrappers = []any{} 166 + type x struct{} 167 + out := protoimpl.TypeBuilder{ 168 + File: protoimpl.DescBuilder{ 169 + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 170 + RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_dns_monitor_proto_rawDesc), len(file_private_location_v1_dns_monitor_proto_rawDesc)), 171 + NumEnums: 0, 172 + NumMessages: 1, 173 + NumExtensions: 0, 174 + NumServices: 0, 175 + }, 176 + GoTypes: file_private_location_v1_dns_monitor_proto_goTypes, 177 + DependencyIndexes: file_private_location_v1_dns_monitor_proto_depIdxs, 178 + MessageInfos: file_private_location_v1_dns_monitor_proto_msgTypes, 179 + }.Build() 180 + File_private_location_v1_dns_monitor_proto = out.File 181 + file_private_location_v1_dns_monitor_proto_goTypes = nil 182 + file_private_location_v1_dns_monitor_proto_depIdxs = nil 183 + }
+29
apps/private-location/proto/private_location/v1/private_location.connect.go
··· 41 41 // PrivateLocationServiceIngestHTTPProcedure is the fully-qualified name of the 42 42 // PrivateLocationService's IngestHTTP RPC. 43 43 PrivateLocationServiceIngestHTTPProcedure = "/private_location.v1.PrivateLocationService/IngestHTTP" 44 + // PrivateLocationServiceIngestDNSProcedure is the fully-qualified name of the 45 + // PrivateLocationService's IngestDNS RPC. 46 + PrivateLocationServiceIngestDNSProcedure = "/private_location.v1.PrivateLocationService/IngestDNS" 44 47 ) 45 48 46 49 // PrivateLocationServiceClient is a client for the private_location.v1.PrivateLocationService ··· 49 52 Monitors(context.Context, *connect.Request[MonitorsRequest]) (*connect.Response[MonitorsResponse], error) 50 53 IngestTCP(context.Context, *connect.Request[IngestTCPRequest]) (*connect.Response[IngestTCPResponse], error) 51 54 IngestHTTP(context.Context, *connect.Request[IngestHTTPRequest]) (*connect.Response[IngestHTTPResponse], error) 55 + IngestDNS(context.Context, *connect.Request[IngestDNSRequest]) (*connect.Response[IngestDNSResponse], error) 52 56 } 53 57 54 58 // NewPrivateLocationServiceClient constructs a client for the ··· 80 84 connect.WithSchema(privateLocationServiceMethods.ByName("IngestHTTP")), 81 85 connect.WithClientOptions(opts...), 82 86 ), 87 + ingestDNS: connect.NewClient[IngestDNSRequest, IngestDNSResponse]( 88 + httpClient, 89 + baseURL+PrivateLocationServiceIngestDNSProcedure, 90 + connect.WithSchema(privateLocationServiceMethods.ByName("IngestDNS")), 91 + connect.WithClientOptions(opts...), 92 + ), 83 93 } 84 94 } 85 95 ··· 88 98 monitors *connect.Client[MonitorsRequest, MonitorsResponse] 89 99 ingestTCP *connect.Client[IngestTCPRequest, IngestTCPResponse] 90 100 ingestHTTP *connect.Client[IngestHTTPRequest, IngestHTTPResponse] 101 + ingestDNS *connect.Client[IngestDNSRequest, IngestDNSResponse] 91 102 } 92 103 93 104 // Monitors calls private_location.v1.PrivateLocationService.Monitors. ··· 105 116 return c.ingestHTTP.CallUnary(ctx, req) 106 117 } 107 118 119 + // IngestDNS calls private_location.v1.PrivateLocationService.IngestDNS. 120 + func (c *privateLocationServiceClient) IngestDNS(ctx context.Context, req *connect.Request[IngestDNSRequest]) (*connect.Response[IngestDNSResponse], error) { 121 + return c.ingestDNS.CallUnary(ctx, req) 122 + } 123 + 108 124 // PrivateLocationServiceHandler is an implementation of the 109 125 // private_location.v1.PrivateLocationService service. 110 126 type PrivateLocationServiceHandler interface { 111 127 Monitors(context.Context, *connect.Request[MonitorsRequest]) (*connect.Response[MonitorsResponse], error) 112 128 IngestTCP(context.Context, *connect.Request[IngestTCPRequest]) (*connect.Response[IngestTCPResponse], error) 113 129 IngestHTTP(context.Context, *connect.Request[IngestHTTPRequest]) (*connect.Response[IngestHTTPResponse], error) 130 + IngestDNS(context.Context, *connect.Request[IngestDNSRequest]) (*connect.Response[IngestDNSResponse], error) 114 131 } 115 132 116 133 // NewPrivateLocationServiceHandler builds an HTTP handler from the service implementation. It ··· 138 155 connect.WithSchema(privateLocationServiceMethods.ByName("IngestHTTP")), 139 156 connect.WithHandlerOptions(opts...), 140 157 ) 158 + privateLocationServiceIngestDNSHandler := connect.NewUnaryHandler( 159 + PrivateLocationServiceIngestDNSProcedure, 160 + svc.IngestDNS, 161 + connect.WithSchema(privateLocationServiceMethods.ByName("IngestDNS")), 162 + connect.WithHandlerOptions(opts...), 163 + ) 141 164 return "/private_location.v1.PrivateLocationService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 142 165 switch r.URL.Path { 143 166 case PrivateLocationServiceMonitorsProcedure: ··· 146 169 privateLocationServiceIngestTCPHandler.ServeHTTP(w, r) 147 170 case PrivateLocationServiceIngestHTTPProcedure: 148 171 privateLocationServiceIngestHTTPHandler.ServeHTTP(w, r) 172 + case PrivateLocationServiceIngestDNSProcedure: 173 + privateLocationServiceIngestDNSHandler.ServeHTTP(w, r) 149 174 default: 150 175 http.NotFound(w, r) 151 176 } ··· 166 191 func (UnimplementedPrivateLocationServiceHandler) IngestHTTP(context.Context, *connect.Request[IngestHTTPRequest]) (*connect.Response[IngestHTTPResponse], error) { 167 192 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("private_location.v1.PrivateLocationService.IngestHTTP is not implemented")) 168 193 } 194 + 195 + func (UnimplementedPrivateLocationServiceHandler) IngestDNS(context.Context, *connect.Request[IngestDNSRequest]) (*connect.Response[IngestDNSResponse], error) { 196 + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("private_location.v1.PrivateLocationService.IngestDNS is not implemented")) 197 + }
+267 -22
apps/private-location/proto/private_location/v1/private_location.pb.go
··· 9 9 import ( 10 10 protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 11 protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 + _ "google.golang.org/protobuf/types/known/structpb" 12 13 reflect "reflect" 13 14 sync "sync" 14 15 unsafe "unsafe" ··· 61 62 state protoimpl.MessageState `protogen:"open.v1"` 62 63 HttpMonitors []*HTTPMonitor `protobuf:"bytes,1,rep,name=http_monitors,json=httpMonitors,proto3" json:"http_monitors,omitempty"` 63 64 TcpMonitors []*TCPMonitor `protobuf:"bytes,2,rep,name=tcp_monitors,json=tcpMonitors,proto3" json:"tcp_monitors,omitempty"` 65 + DnsMonitors []*DNSMonitor `protobuf:"bytes,3,rep,name=dns_monitors,json=dnsMonitors,proto3" json:"dns_monitors,omitempty"` 64 66 unknownFields protoimpl.UnknownFields 65 67 sizeCache protoimpl.SizeCache 66 68 } ··· 105 107 func (x *MonitorsResponse) GetTcpMonitors() []*TCPMonitor { 106 108 if x != nil { 107 109 return x.TcpMonitors 110 + } 111 + return nil 112 + } 113 + 114 + func (x *MonitorsResponse) GetDnsMonitors() []*DNSMonitor { 115 + if x != nil { 116 + return x.DnsMonitors 108 117 } 109 118 return nil 110 119 } ··· 437 446 return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{5} 438 447 } 439 448 449 + type Records struct { 450 + state protoimpl.MessageState `protogen:"open.v1"` 451 + Record []string `protobuf:"bytes,1,rep,name=record,proto3" json:"record,omitempty"` 452 + unknownFields protoimpl.UnknownFields 453 + sizeCache protoimpl.SizeCache 454 + } 455 + 456 + func (x *Records) Reset() { 457 + *x = Records{} 458 + mi := &file_private_location_v1_private_location_proto_msgTypes[6] 459 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 460 + ms.StoreMessageInfo(mi) 461 + } 462 + 463 + func (x *Records) String() string { 464 + return protoimpl.X.MessageStringOf(x) 465 + } 466 + 467 + func (*Records) ProtoMessage() {} 468 + 469 + func (x *Records) ProtoReflect() protoreflect.Message { 470 + mi := &file_private_location_v1_private_location_proto_msgTypes[6] 471 + if x != nil { 472 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 473 + if ms.LoadMessageInfo() == nil { 474 + ms.StoreMessageInfo(mi) 475 + } 476 + return ms 477 + } 478 + return mi.MessageOf(x) 479 + } 480 + 481 + // Deprecated: Use Records.ProtoReflect.Descriptor instead. 482 + func (*Records) Descriptor() ([]byte, []int) { 483 + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{6} 484 + } 485 + 486 + func (x *Records) GetRecord() []string { 487 + if x != nil { 488 + return x.Record 489 + } 490 + return nil 491 + } 492 + 493 + type IngestDNSRequest struct { 494 + state protoimpl.MessageState `protogen:"open.v1"` 495 + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 496 + MonitorId string `protobuf:"bytes,2,opt,name=monitorId,proto3" json:"monitorId,omitempty"` 497 + Latency int64 `protobuf:"varint,3,opt,name=latency,proto3" json:"latency,omitempty"` 498 + Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` 499 + CronTimestamp int64 `protobuf:"varint,5,opt,name=cronTimestamp,proto3" json:"cronTimestamp,omitempty"` 500 + Uri string `protobuf:"bytes,6,opt,name=uri,proto3" json:"uri,omitempty"` 501 + RequestStatus string `protobuf:"bytes,7,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` 502 + Message string `protobuf:"bytes,8,opt,name=message,proto3" json:"message,omitempty"` 503 + Records map[string]*Records `protobuf:"bytes,9,rep,name=records,proto3" json:"records,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` 504 + Timing string `protobuf:"bytes,10,opt,name=timing,proto3" json:"timing,omitempty"` 505 + Error int64 `protobuf:"varint,11,opt,name=error,proto3" json:"error,omitempty"` 506 + unknownFields protoimpl.UnknownFields 507 + sizeCache protoimpl.SizeCache 508 + } 509 + 510 + func (x *IngestDNSRequest) Reset() { 511 + *x = IngestDNSRequest{} 512 + mi := &file_private_location_v1_private_location_proto_msgTypes[7] 513 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 514 + ms.StoreMessageInfo(mi) 515 + } 516 + 517 + func (x *IngestDNSRequest) String() string { 518 + return protoimpl.X.MessageStringOf(x) 519 + } 520 + 521 + func (*IngestDNSRequest) ProtoMessage() {} 522 + 523 + func (x *IngestDNSRequest) ProtoReflect() protoreflect.Message { 524 + mi := &file_private_location_v1_private_location_proto_msgTypes[7] 525 + if x != nil { 526 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 527 + if ms.LoadMessageInfo() == nil { 528 + ms.StoreMessageInfo(mi) 529 + } 530 + return ms 531 + } 532 + return mi.MessageOf(x) 533 + } 534 + 535 + // Deprecated: Use IngestDNSRequest.ProtoReflect.Descriptor instead. 536 + func (*IngestDNSRequest) Descriptor() ([]byte, []int) { 537 + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{7} 538 + } 539 + 540 + func (x *IngestDNSRequest) GetId() string { 541 + if x != nil { 542 + return x.Id 543 + } 544 + return "" 545 + } 546 + 547 + func (x *IngestDNSRequest) GetMonitorId() string { 548 + if x != nil { 549 + return x.MonitorId 550 + } 551 + return "" 552 + } 553 + 554 + func (x *IngestDNSRequest) GetLatency() int64 { 555 + if x != nil { 556 + return x.Latency 557 + } 558 + return 0 559 + } 560 + 561 + func (x *IngestDNSRequest) GetTimestamp() int64 { 562 + if x != nil { 563 + return x.Timestamp 564 + } 565 + return 0 566 + } 567 + 568 + func (x *IngestDNSRequest) GetCronTimestamp() int64 { 569 + if x != nil { 570 + return x.CronTimestamp 571 + } 572 + return 0 573 + } 574 + 575 + func (x *IngestDNSRequest) GetUri() string { 576 + if x != nil { 577 + return x.Uri 578 + } 579 + return "" 580 + } 581 + 582 + func (x *IngestDNSRequest) GetRequestStatus() string { 583 + if x != nil { 584 + return x.RequestStatus 585 + } 586 + return "" 587 + } 588 + 589 + func (x *IngestDNSRequest) GetMessage() string { 590 + if x != nil { 591 + return x.Message 592 + } 593 + return "" 594 + } 595 + 596 + func (x *IngestDNSRequest) GetRecords() map[string]*Records { 597 + if x != nil { 598 + return x.Records 599 + } 600 + return nil 601 + } 602 + 603 + func (x *IngestDNSRequest) GetTiming() string { 604 + if x != nil { 605 + return x.Timing 606 + } 607 + return "" 608 + } 609 + 610 + func (x *IngestDNSRequest) GetError() int64 { 611 + if x != nil { 612 + return x.Error 613 + } 614 + return 0 615 + } 616 + 617 + type IngestDNSResponse struct { 618 + state protoimpl.MessageState `protogen:"open.v1"` 619 + unknownFields protoimpl.UnknownFields 620 + sizeCache protoimpl.SizeCache 621 + } 622 + 623 + func (x *IngestDNSResponse) Reset() { 624 + *x = IngestDNSResponse{} 625 + mi := &file_private_location_v1_private_location_proto_msgTypes[8] 626 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 627 + ms.StoreMessageInfo(mi) 628 + } 629 + 630 + func (x *IngestDNSResponse) String() string { 631 + return protoimpl.X.MessageStringOf(x) 632 + } 633 + 634 + func (*IngestDNSResponse) ProtoMessage() {} 635 + 636 + func (x *IngestDNSResponse) ProtoReflect() protoreflect.Message { 637 + mi := &file_private_location_v1_private_location_proto_msgTypes[8] 638 + if x != nil { 639 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 640 + if ms.LoadMessageInfo() == nil { 641 + ms.StoreMessageInfo(mi) 642 + } 643 + return ms 644 + } 645 + return mi.MessageOf(x) 646 + } 647 + 648 + // Deprecated: Use IngestDNSResponse.ProtoReflect.Descriptor instead. 649 + func (*IngestDNSResponse) Descriptor() ([]byte, []int) { 650 + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{8} 651 + } 652 + 440 653 var File_private_location_v1_private_location_proto protoreflect.FileDescriptor 441 654 442 655 const file_private_location_v1_private_location_proto_rawDesc = "" + 443 656 "\n" + 444 - "*private_location/v1/private_location.proto\x12\x13private_location.v1\x1a&private_location/v1/http_monitor.proto\x1a%private_location/v1/tcp_monitor.proto\"\x11\n" + 445 - "\x0fMonitorsRequest\"\x9d\x01\n" + 657 + "*private_location/v1/private_location.proto\x12\x13private_location.v1\x1a\x1cgoogle/protobuf/struct.proto\x1a%private_location/v1/dns_monitor.proto\x1a&private_location/v1/http_monitor.proto\x1a%private_location/v1/tcp_monitor.proto\"\x11\n" + 658 + "\x0fMonitorsRequest\"\xe1\x01\n" + 446 659 "\x10MonitorsResponse\x12E\n" + 447 660 "\rhttp_monitors\x18\x01 \x03(\v2 .private_location.v1.HTTPMonitorR\fhttpMonitors\x12B\n" + 448 - "\ftcp_monitors\x18\x02 \x03(\v2\x1f.private_location.v1.TCPMonitorR\vtcpMonitors\"\x9e\x02\n" + 661 + "\ftcp_monitors\x18\x02 \x03(\v2\x1f.private_location.v1.TCPMonitorR\vtcpMonitors\x12B\n" + 662 + "\fdns_monitors\x18\x03 \x03(\v2\x1f.private_location.v1.DNSMonitorR\vdnsMonitors\"\x9e\x02\n" + 449 663 "\x10IngestTCPRequest\x12\x0e\n" + 450 664 "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + 451 665 "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + ··· 476 690 "statusCode\x18\f \x01(\x03R\n" + 477 691 "statusCode\x12\x14\n" + 478 692 "\x05error\x18\r \x01(\x03R\x05error\"\x14\n" + 479 - "\x12IngestHTTPResponse2\xb2\x02\n" + 693 + "\x12IngestHTTPResponse\"!\n" + 694 + "\aRecords\x12\x16\n" + 695 + "\x06record\x18\x01 \x03(\tR\x06record\"\xc6\x03\n" + 696 + "\x10IngestDNSRequest\x12\x0e\n" + 697 + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + 698 + "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + 699 + "\alatency\x18\x03 \x01(\x03R\alatency\x12\x1c\n" + 700 + "\ttimestamp\x18\x04 \x01(\x03R\ttimestamp\x12$\n" + 701 + "\rcronTimestamp\x18\x05 \x01(\x03R\rcronTimestamp\x12\x10\n" + 702 + "\x03uri\x18\x06 \x01(\tR\x03uri\x12$\n" + 703 + "\rrequestStatus\x18\a \x01(\tR\rrequestStatus\x12\x18\n" + 704 + "\amessage\x18\b \x01(\tR\amessage\x12L\n" + 705 + "\arecords\x18\t \x03(\v22.private_location.v1.IngestDNSRequest.RecordsEntryR\arecords\x12\x16\n" + 706 + "\x06timing\x18\n" + 707 + " \x01(\tR\x06timing\x12\x14\n" + 708 + "\x05error\x18\v \x01(\x03R\x05error\x1aX\n" + 709 + "\fRecordsEntry\x12\x10\n" + 710 + "\x03key\x18\x01 \x01(\tR\x03key\x122\n" + 711 + "\x05value\x18\x02 \x01(\v2\x1c.private_location.v1.RecordsR\x05value:\x028\x01\"\x13\n" + 712 + "\x11IngestDNSResponse2\x90\x03\n" + 480 713 "\x16PrivateLocationService\x12Y\n" + 481 714 "\bMonitors\x12$.private_location.v1.MonitorsRequest\x1a%.private_location.v1.MonitorsResponse\"\x00\x12\\\n" + 482 715 "\tIngestTCP\x12%.private_location.v1.IngestTCPRequest\x1a&.private_location.v1.IngestTCPResponse\"\x00\x12_\n" + 483 716 "\n" + 484 - "IngestHTTP\x12&.private_location.v1.IngestHTTPRequest\x1a'.private_location.v1.IngestHTTPResponse\"\x00BJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" 717 + "IngestHTTP\x12&.private_location.v1.IngestHTTPRequest\x1a'.private_location.v1.IngestHTTPResponse\"\x00\x12\\\n" + 718 + "\tIngestDNS\x12%.private_location.v1.IngestDNSRequest\x1a&.private_location.v1.IngestDNSResponse\"\x00BJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" 485 719 486 720 var ( 487 721 file_private_location_v1_private_location_proto_rawDescOnce sync.Once ··· 495 729 return file_private_location_v1_private_location_proto_rawDescData 496 730 } 497 731 498 - var file_private_location_v1_private_location_proto_msgTypes = make([]protoimpl.MessageInfo, 6) 732 + var file_private_location_v1_private_location_proto_msgTypes = make([]protoimpl.MessageInfo, 10) 499 733 var file_private_location_v1_private_location_proto_goTypes = []any{ 500 734 (*MonitorsRequest)(nil), // 0: private_location.v1.MonitorsRequest 501 735 (*MonitorsResponse)(nil), // 1: private_location.v1.MonitorsResponse ··· 503 737 (*IngestTCPResponse)(nil), // 3: private_location.v1.IngestTCPResponse 504 738 (*IngestHTTPRequest)(nil), // 4: private_location.v1.IngestHTTPRequest 505 739 (*IngestHTTPResponse)(nil), // 5: private_location.v1.IngestHTTPResponse 506 - (*HTTPMonitor)(nil), // 6: private_location.v1.HTTPMonitor 507 - (*TCPMonitor)(nil), // 7: private_location.v1.TCPMonitor 740 + (*Records)(nil), // 6: private_location.v1.Records 741 + (*IngestDNSRequest)(nil), // 7: private_location.v1.IngestDNSRequest 742 + (*IngestDNSResponse)(nil), // 8: private_location.v1.IngestDNSResponse 743 + nil, // 9: private_location.v1.IngestDNSRequest.RecordsEntry 744 + (*HTTPMonitor)(nil), // 10: private_location.v1.HTTPMonitor 745 + (*TCPMonitor)(nil), // 11: private_location.v1.TCPMonitor 746 + (*DNSMonitor)(nil), // 12: private_location.v1.DNSMonitor 508 747 } 509 748 var file_private_location_v1_private_location_proto_depIdxs = []int32{ 510 - 6, // 0: private_location.v1.MonitorsResponse.http_monitors:type_name -> private_location.v1.HTTPMonitor 511 - 7, // 1: private_location.v1.MonitorsResponse.tcp_monitors:type_name -> private_location.v1.TCPMonitor 512 - 0, // 2: private_location.v1.PrivateLocationService.Monitors:input_type -> private_location.v1.MonitorsRequest 513 - 2, // 3: private_location.v1.PrivateLocationService.IngestTCP:input_type -> private_location.v1.IngestTCPRequest 514 - 4, // 4: private_location.v1.PrivateLocationService.IngestHTTP:input_type -> private_location.v1.IngestHTTPRequest 515 - 1, // 5: private_location.v1.PrivateLocationService.Monitors:output_type -> private_location.v1.MonitorsResponse 516 - 3, // 6: private_location.v1.PrivateLocationService.IngestTCP:output_type -> private_location.v1.IngestTCPResponse 517 - 5, // 7: private_location.v1.PrivateLocationService.IngestHTTP:output_type -> private_location.v1.IngestHTTPResponse 518 - 5, // [5:8] is the sub-list for method output_type 519 - 2, // [2:5] is the sub-list for method input_type 520 - 2, // [2:2] is the sub-list for extension type_name 521 - 2, // [2:2] is the sub-list for extension extendee 522 - 0, // [0:2] is the sub-list for field type_name 749 + 10, // 0: private_location.v1.MonitorsResponse.http_monitors:type_name -> private_location.v1.HTTPMonitor 750 + 11, // 1: private_location.v1.MonitorsResponse.tcp_monitors:type_name -> private_location.v1.TCPMonitor 751 + 12, // 2: private_location.v1.MonitorsResponse.dns_monitors:type_name -> private_location.v1.DNSMonitor 752 + 9, // 3: private_location.v1.IngestDNSRequest.records:type_name -> private_location.v1.IngestDNSRequest.RecordsEntry 753 + 6, // 4: private_location.v1.IngestDNSRequest.RecordsEntry.value:type_name -> private_location.v1.Records 754 + 0, // 5: private_location.v1.PrivateLocationService.Monitors:input_type -> private_location.v1.MonitorsRequest 755 + 2, // 6: private_location.v1.PrivateLocationService.IngestTCP:input_type -> private_location.v1.IngestTCPRequest 756 + 4, // 7: private_location.v1.PrivateLocationService.IngestHTTP:input_type -> private_location.v1.IngestHTTPRequest 757 + 7, // 8: private_location.v1.PrivateLocationService.IngestDNS:input_type -> private_location.v1.IngestDNSRequest 758 + 1, // 9: private_location.v1.PrivateLocationService.Monitors:output_type -> private_location.v1.MonitorsResponse 759 + 3, // 10: private_location.v1.PrivateLocationService.IngestTCP:output_type -> private_location.v1.IngestTCPResponse 760 + 5, // 11: private_location.v1.PrivateLocationService.IngestHTTP:output_type -> private_location.v1.IngestHTTPResponse 761 + 8, // 12: private_location.v1.PrivateLocationService.IngestDNS:output_type -> private_location.v1.IngestDNSResponse 762 + 9, // [9:13] is the sub-list for method output_type 763 + 5, // [5:9] is the sub-list for method input_type 764 + 5, // [5:5] is the sub-list for extension type_name 765 + 5, // [5:5] is the sub-list for extension extendee 766 + 0, // [0:5] is the sub-list for field type_name 523 767 } 524 768 525 769 func init() { file_private_location_v1_private_location_proto_init() } ··· 527 771 if File_private_location_v1_private_location_proto != nil { 528 772 return 529 773 } 774 + file_private_location_v1_dns_monitor_proto_init() 530 775 file_private_location_v1_http_monitor_proto_init() 531 776 file_private_location_v1_tcp_monitor_proto_init() 532 777 type x struct{} ··· 535 780 GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 536 781 RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_private_location_proto_rawDesc), len(file_private_location_v1_private_location_proto_rawDesc)), 537 782 NumEnums: 0, 538 - NumMessages: 6, 783 + NumMessages: 10, 539 784 NumExtensions: 0, 540 785 NumServices: 1, 541 786 },
+4
apps/server/src/routes/v1/monitors/index.ts
··· 6 6 import { registerGetMonitor } from "./get"; 7 7 import { registerGetAllMonitors } from "./get_all"; 8 8 import { registerPostMonitor } from "./post"; 9 + import { registerPostMonitorDNS } from "./post_dns"; 9 10 import { registerPostMonitorHTTP } from "./post_http"; 10 11 import { registerPostMonitorTCP } from "./post_tcp"; 11 12 import { registerPutMonitor } from "./put"; 13 + import { registerPutDNSMonitor } from "./put_dns"; 12 14 import { registerPutHTTPMonitor } from "./put_http"; 13 15 import { registerPutTCPMonitor } from "./put_tcp"; 14 16 import { registerGetMonitorResult } from "./results/get"; ··· 27 29 registerPostMonitor(monitorsApi); 28 30 registerPostMonitorHTTP(monitorsApi); 29 31 registerPostMonitorTCP(monitorsApi); 32 + registerPostMonitorDNS(monitorsApi); 30 33 registerPutHTTPMonitor(monitorsApi); 31 34 registerPutTCPMonitor(monitorsApi); 35 + registerPutDNSMonitor(monitorsApi); 32 36 // 33 37 registerGetMonitorSummary(monitorsApi); 34 38 registerTriggerMonitor(monitorsApi);
+64
apps/server/src/routes/v1/monitors/post_dns.test.ts
··· 1 + import { expect, test } from "bun:test"; 2 + 3 + import { app } from "@/index"; 4 + import { MonitorSchema } from "./schema"; 5 + 6 + test("create a valid monitor", async () => { 7 + const res = await app.request("/v1/monitor/dns", { 8 + method: "POST", 9 + headers: { 10 + "x-openstatus-key": "1", 11 + "content-type": "application/json", 12 + }, 13 + body: JSON.stringify({ 14 + frequency: "10m", 15 + name: "OpenStatus", 16 + description: "OpenStatus website", 17 + regions: ["ams", "gru"], 18 + request: { 19 + uri: "openstatus.dev", 20 + }, 21 + active: true, 22 + public: true, 23 + }), 24 + }); 25 + 26 + const result = MonitorSchema.safeParse(await res.json()); 27 + 28 + expect(res.status).toBe(200); 29 + expect(result.success).toBe(true); 30 + }); 31 + 32 + test("create a status report with invalid payload should return 400", async () => { 33 + const res = await app.request("/v1/monitor/dns", { 34 + method: "POST", 35 + headers: { 36 + "x-openstatus-key": "1", 37 + "content-type": "application/json", 38 + }, 39 + body: JSON.stringify({ 40 + frequency: "21m", 41 + name: "OpenStatus", 42 + description: "OpenStatus website", 43 + regions: ["ams", "gru"], 44 + request: { 45 + url: "openstatus.dev", 46 + }, 47 + active: true, 48 + public: true, 49 + }), 50 + }); 51 + 52 + expect(res.status).toBe(400); 53 + }); 54 + 55 + test("no auth key should return 401", async () => { 56 + const res = await app.request("/v1/monitor/dns", { 57 + method: "POST", 58 + headers: { 59 + "content-type": "application/json", 60 + }, 61 + }); 62 + 63 + expect(res.status).toBe(401); 64 + });
+144
apps/server/src/routes/v1/monitors/post_dns.ts
··· 1 + import { createRoute, z } from "@hono/zod-openapi"; 2 + 3 + import { Events } from "@openstatus/analytics"; 4 + import { and, db, eq, isNull, sql } from "@openstatus/db"; 5 + import { monitor } from "@openstatus/db/src/schema"; 6 + 7 + // import { serialize } from "@openstatus/assertions"; 8 + 9 + import { OpenStatusApiError, openApiErrorResponses } from "@/libs/errors"; 10 + import { trackMiddleware } from "@/libs/middlewares"; 11 + import type { monitorsApi } from "./index"; 12 + import { DNSMonitorSchema, MonitorSchema } from "./schema"; 13 + // import { getAssertionNew } from "./utils"; 14 + 15 + const postRoute = createRoute({ 16 + method: "post", 17 + tags: ["monitor"], 18 + summary: "Create a http monitor", 19 + path: "/dns", 20 + middleware: [trackMiddleware(Events.CreateMonitor, ["url", "jobType"])], 21 + request: { 22 + body: { 23 + description: "The monitor to create", 24 + content: { 25 + "application/json": { 26 + schema: DNSMonitorSchema, 27 + }, 28 + }, 29 + }, 30 + }, 31 + responses: { 32 + 200: { 33 + content: { 34 + "application/json": { 35 + schema: MonitorSchema, 36 + }, 37 + }, 38 + description: "Create a monitor", 39 + }, 40 + ...openApiErrorResponses, 41 + }, 42 + }); 43 + 44 + export function registerPostMonitorDNS(api: typeof monitorsApi) { 45 + return api.openapi(postRoute, async (c) => { 46 + const workspaceId = c.get("workspace").id; 47 + const limits = c.get("workspace").limits; 48 + const input = c.req.valid("json"); 49 + const count = ( 50 + await db 51 + .select({ count: sql<number>`count(*)` }) 52 + .from(monitor) 53 + .where( 54 + and(eq(monitor.workspaceId, workspaceId), isNull(monitor.deletedAt)), 55 + ) 56 + .all() 57 + )[0].count; 58 + 59 + if (count >= limits.monitors) { 60 + throw new OpenStatusApiError({ 61 + code: "PAYMENT_REQUIRED", 62 + message: "Upgrade for more monitors", 63 + }); 64 + } 65 + 66 + if (!limits.periodicity.includes(input.frequency)) { 67 + throw new OpenStatusApiError({ 68 + code: "PAYMENT_REQUIRED", 69 + message: "Upgrade for more periodicity", 70 + }); 71 + } 72 + 73 + if (limits["max-regions"] < input.regions.length) { 74 + throw new OpenStatusApiError({ 75 + code: "PAYMENT_REQUIRED", 76 + message: "Upgrade for more regions", 77 + }); 78 + } 79 + 80 + for (const region of input.regions) { 81 + if (!limits.regions.includes(region)) { 82 + throw new OpenStatusApiError({ 83 + code: "PAYMENT_REQUIRED", 84 + message: "Upgrade for more regions", 85 + }); 86 + } 87 + } 88 + 89 + const { request, regions, assertions, openTelemetry, ...rest } = input; 90 + 91 + const otelHeadersEntries = openTelemetry?.headers 92 + ? Object.entries(openTelemetry.headers).map(([key, value]) => ({ 93 + key: key, 94 + value: value, 95 + })) 96 + : undefined; 97 + 98 + // const assert = assertions ? getAssertionNew(assertions) : []; 99 + 100 + const _newMonitor = await db 101 + .insert(monitor) 102 + .values({ 103 + ...rest, 104 + periodicity: input.frequency, 105 + jobType: "dns", 106 + url: input.request.uri, 107 + workspaceId: workspaceId, 108 + regions: regions ? regions.join(",") : undefined, 109 + // assertions: assert.length > 0 ? serialize(assert) : undefined, 110 + timeout: input.timeout || 45000, 111 + otelEndpoint: openTelemetry?.endpoint, 112 + otelHeaders: otelHeadersEntries 113 + ? JSON.stringify(otelHeadersEntries) 114 + : undefined, 115 + }) 116 + .returning() 117 + .get(); 118 + 119 + const otelHeader = _newMonitor.otelHeaders 120 + ? z 121 + .array( 122 + z.object({ 123 + key: z.string(), 124 + value: z.string(), 125 + }), 126 + ) 127 + .parse(JSON.parse(_newMonitor.otelHeaders)) 128 + // biome-ignore lint/performance/noAccumulatingSpread: <explanation> 129 + .reduce((a, v) => ({ ...a, [v.key]: v.value }), {}) 130 + : undefined; 131 + 132 + const data = MonitorSchema.parse({ 133 + ..._newMonitor, 134 + openTelemetry: _newMonitor.otelEndpoint 135 + ? { 136 + headers: otelHeader, 137 + endpoint: _newMonitor.otelEndpoint ?? undefined, 138 + } 139 + : undefined, 140 + }); 141 + 142 + return c.json(data, 200); 143 + }); 144 + }
+55
apps/server/src/routes/v1/monitors/put_dns.test.ts
··· 1 + import { expect, test } from "bun:test"; 2 + 3 + import { app } from "@/index"; 4 + 5 + test("update the monitor", async () => { 6 + const res = await app.request("/v1/monitor/dns/1", { 7 + method: "PUT", 8 + headers: { 9 + "x-openstatus-key": "1", 10 + "Content-Type": "application/json", 11 + }, 12 + body: JSON.stringify({ 13 + name: "New Name", 14 + }), 15 + }); 16 + 17 + expect(res.status).toBe(400); 18 + }); 19 + 20 + test("invalid monitor id should return 404", async () => { 21 + const res = await app.request("/v1/monitor/dns/404", { 22 + method: "PUT", 23 + headers: { 24 + "x-openstatus-key": "1", 25 + "Content-Type": "application/json", 26 + }, 27 + body: JSON.stringify({ 28 + frequency: "10m", 29 + name: "OpenStatus", 30 + description: "OpenStatus website", 31 + regions: ["ams", "gru"], 32 + request: { 33 + uri: "openstatus.dev", 34 + }, 35 + active: true, 36 + public: true, 37 + }), 38 + }); 39 + 40 + expect(res.status).toBe(404); 41 + }); 42 + 43 + test("no auth key should return 401", async () => { 44 + const res = await app.request("/v1/monitor/dns/2", { 45 + method: "PUT", 46 + headers: { 47 + "content-type": "application/json", 48 + }, 49 + body: JSON.stringify({ 50 + /* */ 51 + }), 52 + }); 53 + 54 + expect(res.status).toBe(401); 55 + });
+143
apps/server/src/routes/v1/monitors/put_dns.ts
··· 1 + import { createRoute, z } from "@hono/zod-openapi"; 2 + 3 + import { and, db, eq, isNull } from "@openstatus/db"; 4 + import { monitor } from "@openstatus/db/src/schema"; 5 + 6 + import { OpenStatusApiError, openApiErrorResponses } from "@/libs/errors"; 7 + import { trackMiddleware } from "@/libs/middlewares"; 8 + import { Events } from "@openstatus/analytics"; 9 + import type { monitorsApi } from "./index"; 10 + import { DNSMonitorSchema, MonitorSchema, ParamsSchema } from "./schema"; 11 + 12 + const putRoute = createRoute({ 13 + method: "put", 14 + tags: ["monitor"], 15 + summary: "Update an DNS monitor", 16 + path: "/dns/{id}", 17 + middleware: [trackMiddleware(Events.UpdateMonitor)], 18 + request: { 19 + params: ParamsSchema, 20 + body: { 21 + description: "The monitor to update", 22 + content: { 23 + "application/json": { 24 + schema: DNSMonitorSchema, 25 + }, 26 + }, 27 + }, 28 + }, 29 + responses: { 30 + 200: { 31 + content: { 32 + "application/json": { 33 + schema: MonitorSchema, 34 + }, 35 + }, 36 + description: "Update a monitor", 37 + }, 38 + ...openApiErrorResponses, 39 + }, 40 + }); 41 + 42 + export function registerPutDNSMonitor(api: typeof monitorsApi) { 43 + return api.openapi(putRoute, async (c) => { 44 + const workspaceId = c.get("workspace").id; 45 + const limits = c.get("workspace").limits; 46 + const { id } = c.req.valid("param"); 47 + const input = c.req.valid("json"); 48 + 49 + if (input.frequency && !limits.periodicity.includes(input.frequency)) { 50 + throw new OpenStatusApiError({ 51 + code: "PAYMENT_REQUIRED", 52 + message: "Upgrade for more periodicity", 53 + }); 54 + } 55 + 56 + if (input.regions) { 57 + for (const region of input.regions) { 58 + if (!limits.regions.includes(region)) { 59 + throw new OpenStatusApiError({ 60 + code: "PAYMENT_REQUIRED", 61 + message: "Upgrade for more regions", 62 + }); 63 + } 64 + } 65 + } 66 + 67 + const _monitor = await db 68 + .select() 69 + .from(monitor) 70 + .where( 71 + and( 72 + eq(monitor.id, Number(id)), 73 + isNull(monitor.deletedAt), 74 + eq(monitor.workspaceId, workspaceId), 75 + ), 76 + ) 77 + .get(); 78 + 79 + if (!_monitor) { 80 + throw new OpenStatusApiError({ 81 + code: "NOT_FOUND", 82 + message: `Monitor ${id} not found`, 83 + }); 84 + } 85 + 86 + if (_monitor.jobType !== "tcp") { 87 + throw new OpenStatusApiError({ 88 + code: "NOT_FOUND", 89 + message: `Monitor ${id} not found`, 90 + }); 91 + } 92 + 93 + const { request, regions, openTelemetry, assertions, ...rest } = input; 94 + 95 + const otelHeadersEntries = openTelemetry?.headers 96 + ? Object.entries(openTelemetry.headers).map(([key, value]) => ({ 97 + key: key, 98 + value: value, 99 + })) 100 + : undefined; 101 + 102 + const _newMonitor = await db 103 + .update(monitor) 104 + .set({ 105 + ...rest, 106 + periodicity: input.frequency, 107 + url: input.request.uri, 108 + regions: regions ? regions.join(",") : undefined, 109 + otelHeaders: otelHeadersEntries 110 + ? JSON.stringify(otelHeadersEntries) 111 + : undefined, 112 + otelEndpoint: openTelemetry?.endpoint, 113 + timeout: input.timeout || 45000, 114 + updatedAt: new Date(), 115 + }) 116 + .where(eq(monitor.id, Number(_monitor.id))) 117 + .returning() 118 + .get(); 119 + const otelHeader = _newMonitor.otelHeaders 120 + ? z 121 + .array( 122 + z.object({ 123 + key: z.string(), 124 + value: z.string(), 125 + }), 126 + ) 127 + .parse(JSON.parse(_newMonitor.otelHeaders)) 128 + // biome-ignore lint/performance/noAccumulatingSpread: <explanation> 129 + .reduce((a, v) => ({ ...a, [v.key]: v.value }), {}) 130 + : undefined; 131 + 132 + const data = MonitorSchema.parse({ 133 + ..._newMonitor, 134 + openTelemetry: _newMonitor.otelEndpoint 135 + ? { 136 + headers: otelHeader, 137 + endpoint: _newMonitor.otelEndpoint ?? undefined, 138 + } 139 + : undefined, 140 + }); 141 + return c.json(data, 200); 142 + }); 143 + }
+40 -1
apps/server/src/routes/v1/monitors/schema.ts
··· 1 1 import { z } from "@hono/zod-openapi"; 2 2 3 - import { numberCompare, stringCompare } from "@openstatus/assertions"; 3 + import { 4 + numberCompare, 5 + recordCompare, 6 + stringCompare, 7 + } from "@openstatus/assertions"; 4 8 import { monitorJobTypes, monitorMethods } from "@openstatus/db/src/schema"; 5 9 import { 6 10 monitorPeriodicitySchema, ··· 454 458 }), 455 459 }); 456 460 461 + const dnsRequestSchema = z.object({ 462 + uri: z.string().openapi({ 463 + description: "The DNS server to query", 464 + examples: ["openstatus.dev"], 465 + }), 466 + }); 467 + 457 468 const statusCodeAssertion = z 458 469 .object({ 459 470 kind: z.literal("statusCode"), ··· 514 525 }), 515 526 }); 516 527 528 + const dnsRecordAssertion = z.object({ 529 + kind: z.literal("dnsRecord"), 530 + recordType: z.enum(["A", "AAAA", "CNAME", "MX", "TXT"]).openapi({ 531 + description: "Type of DNS record to check", 532 + examples: ["A", "CNAME"], 533 + }), 534 + compare: recordCompare.openapi({ 535 + description: "Comparison operator", 536 + examples: ["eq", "not_eq", "contains", "not_contains"], 537 + }), 538 + target: z.string().openapi({ 539 + description: "DNS record value to assert", 540 + examples: ["example.com"], 541 + }), 542 + }); 517 543 export const assertionsSchema = z.discriminatedUnion("kind", [ 518 544 statusCodeAssertion, 519 545 headerAssertions, ··· 542 568 .openapi({ 543 569 title: "TCP Monitor Schema", 544 570 }); 571 + 572 + export const DNSMonitorSchema = baseRequest 573 + .extend({ 574 + request: dnsRequestSchema.openapi({ 575 + description: "The DNS Request we are sending", 576 + }), 577 + assertions: z.array(dnsRecordAssertion).optional().openapi({ 578 + description: "Assertions to run on the DNS response", 579 + }), 580 + }) 581 + .openapi({ 582 + title: "DNS Monitor Schema", 583 + });
-1
apps/ssh-server/main.go
··· 114 114 115 115 } 116 116 ssh.HostKeyFile("/data/id_rsa") 117 - // server.AddHostKey(ssh.HostKeyFile(filepath string)) 118 117 119 118 log.Println("starting ssh server on port 2222...") 120 119 log.Fatal(server.ListenAndServe())
+22
apps/workflows/src/cron/checker.ts
··· 20 20 import { getLogger } from "@logtape/logtape"; 21 21 import type { monitorPeriodicitySchema } from "@openstatus/db/src/schema/constants"; 22 22 import { 23 + type DNSPayloadSchema, 23 24 type httpPayloadSchema, 24 25 type tpcPayloadSchema, 25 26 transformHeaders, ··· 193 194 let payload: 194 195 | z.infer<typeof httpPayloadSchema> 195 196 | z.infer<typeof tpcPayloadSchema> 197 + | z.infer<typeof DNSPayloadSchema> 196 198 | null = null; 197 199 198 200 // ··· 238 240 headers: transformHeaders(row.otelHeaders), 239 241 } 240 242 : undefined, 243 + }; 244 + } 245 + if (row.jobType === "dns") { 246 + payload = { 247 + workspaceId: String(row.workspaceId), 248 + monitorId: String(row.id), 249 + url: row.url, 250 + cronTimestamp: timestamp, 251 + status: status, 252 + assertions: row.assertions ? JSON.parse(row.assertions) : null, 253 + degradedAfter: row.degradedAfter, 254 + timeout: row.timeout, 255 + trigger: "cron", 256 + otelConfig: row.otelEndpoint 257 + ? { 258 + endpoint: row.otelEndpoint, 259 + headers: transformHeaders(row.otelHeaders), 260 + } 261 + : undefined, 262 + retry: row.retry || 3, 241 263 }; 242 264 } 243 265
+16
packages/assertions/src/v1.ts
··· 17 17 ]); 18 18 export const numberCompare = z.enum(["eq", "not_eq", "gt", "gte", "lt", "lte"]); 19 19 20 + export const recordCompare = z.enum([ 21 + "contains", 22 + "not_contains", 23 + "eq", 24 + "not_eq", 25 + ]); 26 + 20 27 function evaluateNumber( 21 28 value: number, 22 29 compare: z.infer<typeof numberCompare>, ··· 195 202 type: z.literal("jsonBody"), 196 203 path: z.string(), // https://www.npmjs.com/package/jsonpath-plus 197 204 compare: stringCompare, 205 + target: z.string(), 206 + }), 207 + ); 208 + 209 + export const recordAssertion = base.merge( 210 + z.object({ 211 + type: z.literal("dnsRecord"), 212 + record: z.enum(["A", "AAAA", "CNAME", "MX", "TXT", "NS"]), 213 + compare: recordCompare, 198 214 target: z.string(), 199 215 }), 200 216 );
+39 -27
packages/proto/private_location/v1/assertions.proto
··· 1 - 2 1 syntax = "proto3"; 3 2 4 3 package private_location.v1; 5 4 6 5 option go_package = "github.com/openstatushq/openstatus/packages/proto/private_location/v1;v1"; 7 6 8 - 9 7 enum NumberComparator { 10 - NUMBER_COMPARATOR_UNSPECIFIED = 0; 11 - NUMBER_COMPARATOR_EQUAL = 1; 12 - NUMBER_COMPARATOR_NOT_EQUAL = 2; 13 - NUMBER_COMPARATOR_GREATER_THAN = 3; 14 - NUMBER_COMPARATOR_GREATER_THAN_OR_EQUAL = 4; 15 - NUMBER_COMPARATOR_LESS_THAN = 5; 16 - NUMBER_COMPARATOR_LESS_THAN_OR_EQUAL = 6; 8 + NUMBER_COMPARATOR_UNSPECIFIED = 0; 9 + NUMBER_COMPARATOR_EQUAL = 1; 10 + NUMBER_COMPARATOR_NOT_EQUAL = 2; 11 + NUMBER_COMPARATOR_GREATER_THAN = 3; 12 + NUMBER_COMPARATOR_GREATER_THAN_OR_EQUAL = 4; 13 + NUMBER_COMPARATOR_LESS_THAN = 5; 14 + NUMBER_COMPARATOR_LESS_THAN_OR_EQUAL = 6; 17 15 } 18 16 19 17 enum StringComparator { 20 - STRING_COMPARATOR_UNSPECIFIED = 0; 21 - STRING_COMPARATOR_CONTAINS = 1; 22 - STRING_COMPARATOR_NOT_CONTAINS = 2; 23 - STRING_COMPARATOR_EQUAL = 3; 24 - STRING_COMPARATOR_NOT_EQUAL = 4; 25 - STRING_COMPARATOR_EMPTY = 5; 26 - STRING_COMPARATOR_NOT_EMPTY = 6; 27 - STRING_COMPARATOR_GREATER_THAN = 7; 28 - STRING_COMPARATOR_GREATER_THAN_OR_EQUAL = 8; 29 - STRING_COMPARATOR_LESS_THAN = 9; 30 - STRING_COMPARATOR_LESS_THAN_OR_EQUAL = 10; 18 + STRING_COMPARATOR_UNSPECIFIED = 0; 19 + STRING_COMPARATOR_CONTAINS = 1; 20 + STRING_COMPARATOR_NOT_CONTAINS = 2; 21 + STRING_COMPARATOR_EQUAL = 3; 22 + STRING_COMPARATOR_NOT_EQUAL = 4; 23 + STRING_COMPARATOR_EMPTY = 5; 24 + STRING_COMPARATOR_NOT_EMPTY = 6; 25 + STRING_COMPARATOR_GREATER_THAN = 7; 26 + STRING_COMPARATOR_GREATER_THAN_OR_EQUAL = 8; 27 + STRING_COMPARATOR_LESS_THAN = 9; 28 + STRING_COMPARATOR_LESS_THAN_OR_EQUAL = 10; 29 + } 30 + 31 + enum RecordComparator { 32 + RECORD_COMPARATOR_UNSPECIFIED = 0; 33 + RECORD_COMPARATOR_EQUAL = 1; 34 + RECORD_COMPARATOR_NOT_EQUAL = 2; 35 + RECORD_COMPARATOR_CONTAINS = 3; 36 + RECORD_COMPARATOR_NOT_CONTAINS = 4; 31 37 } 32 38 33 39 message StatusCodeAssertion { 34 - int64 target = 1; 35 - NumberComparator comparator = 2; 40 + int64 target = 1; 41 + NumberComparator comparator = 2; 36 42 } 37 43 38 44 message BodyAssertion { 39 - string target = 1; 40 - StringComparator comparator = 2; 45 + string target = 1; 46 + StringComparator comparator = 2; 41 47 } 42 48 43 49 message HeaderAssertion { 44 - string target = 1; 45 - StringComparator comparator = 2; 46 - string key = 3; 50 + string target = 1; 51 + StringComparator comparator = 2; 52 + string key = 3; 53 + } 54 + 55 + message RecordAssertion { 56 + string record = 1; 57 + RecordComparator comparator = 2; 58 + string targert = 3; 47 59 }
+19
packages/proto/private_location/v1/dns_monitor.proto
··· 1 + syntax = "proto3"; 2 + 3 + package private_location.v1; 4 + 5 + import "private_location/v1/assertions.proto"; 6 + 7 + option go_package = "github.com/openstatushq/openstatus/packages/proto/private_location/v1;v1"; 8 + 9 + message DNSMonitor { 10 + string id = 1; 11 + string uri = 2; 12 + int64 timeout = 3; 13 + optional int64 degraded_at = 4; 14 + string periodicity = 5; 15 + int64 retry = 6; 16 + 17 + repeated RecordAssertion record_assertions = 13; 18 + 19 + }
+27
packages/proto/private_location/v1/private_location.proto
··· 2 2 3 3 package private_location.v1; 4 4 5 + import "google/protobuf/struct.proto"; 6 + 7 + import "private_location/v1/dns_monitor.proto"; 5 8 import "private_location/v1/http_monitor.proto"; 6 9 import "private_location/v1/tcp_monitor.proto"; 7 10 ··· 12 15 rpc Monitors(MonitorsRequest) returns (MonitorsResponse) {} 13 16 rpc IngestTCP(IngestTCPRequest) returns (IngestTCPResponse) {} 14 17 rpc IngestHTTP(IngestHTTPRequest) returns (IngestHTTPResponse) {} 18 + rpc IngestDNS(IngestDNSRequest) returns (IngestDNSResponse) {} 19 + 15 20 } 16 21 17 22 message MonitorsRequest {} ··· 19 24 message MonitorsResponse { 20 25 repeated HTTPMonitor http_monitors = 1; 21 26 repeated TCPMonitor tcp_monitors = 2; 27 + repeated DNSMonitor dns_monitors = 3; 22 28 } 23 29 24 30 ··· 59 65 message IngestHTTPResponse { 60 66 61 67 } 68 + 69 + 70 + message Records { 71 + repeated string record = 1; 72 + } 73 + message IngestDNSRequest { 74 + string id = 1; 75 + string monitorId = 2; 76 + int64 latency = 3; 77 + int64 timestamp = 4; 78 + int64 cronTimestamp = 5; 79 + string uri = 6; 80 + string requestStatus = 7; 81 + string message = 8; 82 + map<string, Records> records = 9; 83 + string timing = 10; 84 + int64 error = 11; 85 + } 86 + message IngestDNSResponse { 87 + 88 + }
+21
packages/utils/index.ts
··· 49 49 50 50 export type TcpPayload = z.infer<typeof tpcPayloadSchema>; 51 51 52 + export const DNSPayloadSchema = z.object({ 53 + status: z.enum(monitorStatus), 54 + workspaceId: z.string(), 55 + uri: z.string(), 56 + monitorId: z.string(), 57 + assertions: z.array(base).nullable(), 58 + cronTimestamp: z.number(), 59 + timeout: z.number().default(45000), 60 + degradedAfter: z.number().nullable(), 61 + trigger: z.enum(["cron", "api"]).optional().nullable().default("cron"), 62 + otelConfig: z 63 + .object({ 64 + endpoint: z.string(), 65 + headers: z.record(z.string()), 66 + }) 67 + .optional(), 68 + retry: z.number().default(3), 69 + }); 70 + 71 + export type DNSPayload = z.infer<typeof DNSPayloadSchema>; 72 + 52 73 export function transformHeaders(headers: { key: string; value: string }[]) { 53 74 return headers.length > 0 54 75 ? headers.reduce(