From 58d386c76e0d5a6c427cf1154f48f9af5287d95c Mon Sep 17 00:00:00 2001 From: Thibault Le Ouay Date: Mon, 13 Jul 2026 09:24:39 +0200 Subject: [PATCH] feat: send OTel metrics from private-location checkers (#2366) Private-location checkers now emit OpenTelemetry metrics for HTTP and TCP checks to the customer-configured OTLP endpoint, matching the public checker. The private-location server propagates each monitor's OTel config plus the location name (region) over the Monitors RPC, and the checker jobs populate OtelConfig and call the existing pkg/otel recorders. - proto: shared OtelConfig message on HTTPMonitor/TCPMonitor; region on MonitorsResponse - private-location: select + map otel_endpoint/otel_headers, return region = location name - checker: HTTPJob/TCPJob record HTTP/TCP metrics; error counter emitted on hard failure - tests: OTLP export wiring (checker) + Monitors region/otel mapping (private-location) DNS is out of scope (DNSJob is currently unimplemented). Claude-Session: https://claude.ai/code/session_01QyhZyvvNzES3koSoX9rWjh Co-authored-by: Claude Opus 4.8 (1M context) --- apps/checker/pkg/job/http_job.go | 20 +- apps/checker/pkg/job/http_job_test.go | 4 +- apps/checker/pkg/job/job.go | 17 +- apps/checker/pkg/job/otel_wiring_test.go | 169 ++++++++++++++++ apps/checker/pkg/job/tcp_job.go | 35 +++- apps/checker/pkg/job/tcp_job_test.go | 4 +- apps/checker/pkg/scheduler/scheduler.go | 4 +- apps/checker/pkg/scheduler/scheduler_test.go | 34 +++- .../private_location/v1/http_monitor.pb.go | 100 +++------ .../proto/private_location/v1/otel.pb.go | 189 ++++++++++++++++++ .../v1/private_location.pb.go | 16 +- .../private_location/v1/tcp_monitor.pb.go | 27 ++- .../internal/database/models.go | 3 +- .../internal/server/db_testdata | 2 +- .../internal/server/monitors.go | 172 ++++++++++------ .../internal/server/otel_test.go | 85 ++++++++ .../private_location/v1/http_monitor.pb.go | 100 +++------ .../proto/private_location/v1/otel.pb.go | 189 ++++++++++++++++++ .../v1/private_location.pb.go | 16 +- .../private_location/v1/tcp_monitor.pb.go | 27 ++- .../private_location/v1/http_monitor.proto | 8 +- .../internal/private_location/v1/otel.proto | 15 ++ .../v1/private_location.proto | 1 + .../private_location/v1/tcp_monitor.proto | 4 + 24 files changed, 1000 insertions(+), 241 deletions(-) create mode 100644 apps/checker/pkg/job/otel_wiring_test.go create mode 100644 apps/checker/proto/private_location/v1/otel.pb.go create mode 100644 apps/private-location/internal/server/otel_test.go create mode 100644 apps/private-location/proto/private_location/v1/otel.pb.go create mode 100644 packages/proto/internal/private_location/v1/otel.proto diff --git a/apps/checker/pkg/job/http_job.go b/apps/checker/pkg/job/http_job.go index 1e2dab1f..781c6eaa 100644 --- a/apps/checker/pkg/job/http_job.go +++ b/apps/checker/pkg/job/http_job.go @@ -11,6 +11,7 @@ import ( "github.com/google/uuid" "github.com/openstatushq/openstatus/apps/checker/checker" "github.com/openstatushq/openstatus/apps/checker/pkg/assertions" + "github.com/openstatushq/openstatus/apps/checker/pkg/otel" v1 "github.com/openstatushq/openstatus/apps/checker/proto/private_location/v1" "github.com/openstatushq/openstatus/apps/checker/request" ) @@ -53,7 +54,7 @@ func ProtoStringAssertionToComparator(assertion v1.StringComparator) (request.St return "", fmt.Errorf("unknown comparator type: %v", assertion) } -func (jr jobRunner) HTTPJob(ctx context.Context, monitor *v1.HTTPMonitor) (*HttpPrivateRegionData, error) { +func (jr jobRunner) HTTPJob(ctx context.Context, monitor *v1.HTTPMonitor, region string) (*HttpPrivateRegionData, error) { retry := monitor.Retry if retry == 0 { @@ -110,8 +111,13 @@ func (jr jobRunner) HTTPJob(ctx context.Context, monitor *v1.HTTPMonitor) (*Http FollowRedirects: monitor.FollowRedirects, Headers: headers, } + if otelCfg := monitor.GetOtelConfig(); otelCfg.GetEndpoint() != "" { + req.OtelConfig.Endpoint = otelCfg.GetEndpoint() + req.OtelConfig.Headers = headersToMap(otelCfg.GetHeaders()) + } var called int + var lastRes checker.Response op := func() (*HttpPrivateRegionData, error) { called++ @@ -119,6 +125,7 @@ func (jr jobRunner) HTTPJob(ctx context.Context, monitor *v1.HTTPMonitor) (*Http if err != nil { return nil, fmt.Errorf("unable to ping: %w", err) } + lastRes = res timingBytes, err := json.Marshal(res.Timing) if err != nil { @@ -212,6 +219,9 @@ func (jr jobRunner) HTTPJob(ctx context.Context, monitor *v1.HTTPMonitor) (*Http } } else { data.Error = 1 + // Mark the recorded response as errored so OTel emits the error counter + // for non-2xx / failed assertions, matching the public checker. + lastRes.Error = "Error" if called < int(retry) { return nil, fmt.Errorf("unable to ping: %v with status %v", res, res.Status) } @@ -221,6 +231,14 @@ func (jr jobRunner) HTTPJob(ctx context.Context, monitor *v1.HTTPMonitor) (*Http } resp, err := backoff.Retry(ctx, op, backoff.WithMaxTries(uint(retry)), backoff.WithBackOff(backoff.NewExponentialBackOff())) + + if req.OtelConfig.Endpoint != "" { + if err != nil && lastRes.Error == "" { + lastRes.Error = err.Error() + } + otel.RecordHTTPMetrics(ctx, req, lastRes, region) + } + if err != nil { return nil, err } diff --git a/apps/checker/pkg/job/http_job_test.go b/apps/checker/pkg/job/http_job_test.go index d1078c97..2ed660ac 100644 --- a/apps/checker/pkg/job/http_job_test.go +++ b/apps/checker/pkg/job/http_job_test.go @@ -23,7 +23,7 @@ func TestHTTPJob_Success(t *testing.T) { Retry: 2, } - data, err := job.NewJobRunner().HTTPJob(context.Background(), monitor) + data, err := job.NewJobRunner().HTTPJob(context.Background(), monitor, "test-region") if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -44,7 +44,7 @@ func TestHTTPJob_Failure(t *testing.T) { Retry: 1, } - data, err := job.NewJobRunner().HTTPJob(context.Background(), monitor) + data, err := job.NewJobRunner().HTTPJob(context.Background(), monitor, "test-region") if err == nil { t.Fatalf("expected error, got nil") } diff --git a/apps/checker/pkg/job/job.go b/apps/checker/pkg/job/job.go index 07353437..944e4de0 100644 --- a/apps/checker/pkg/job/job.go +++ b/apps/checker/pkg/job/job.go @@ -28,8 +28,8 @@ type HttpPrivateRegionData struct { } type JobRunner interface { - TCPJob(ctx context.Context, monitor *v1.TCPMonitor) (*TCPPrivateRegionData, error) - HTTPJob(ctx context.Context, monitor *v1.HTTPMonitor) (*HttpPrivateRegionData, error) + TCPJob(ctx context.Context, monitor *v1.TCPMonitor, region string) (*TCPPrivateRegionData, error) + HTTPJob(ctx context.Context, monitor *v1.HTTPMonitor, region string) (*HttpPrivateRegionData, error) DNSJob(ctx context.Context, monitor *v1.DNSMonitor) (*DNSPrivateRegionData, error) } @@ -38,3 +38,16 @@ type jobRunner struct{} func NewJobRunner() JobRunner { return &jobRunner{} } + +func headersToMap(headers []*v1.Headers) map[string]string { + if len(headers) == 0 { + return nil + } + + m := make(map[string]string, len(headers)) + for _, header := range headers { + m[header.GetKey()] = header.GetValue() + } + + return m +} diff --git a/apps/checker/pkg/job/otel_wiring_test.go b/apps/checker/pkg/job/otel_wiring_test.go new file mode 100644 index 00000000..84de16c0 --- /dev/null +++ b/apps/checker/pkg/job/otel_wiring_test.go @@ -0,0 +1,169 @@ +package job_test + +import ( + "bytes" + "context" + "io" + "net/http" + "net/http/httptest" + "strings" + "sync" + "testing" + "time" + + "github.com/openstatushq/openstatus/apps/checker/pkg/job" + v1 "github.com/openstatushq/openstatus/apps/checker/proto/private_location/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// fakeOTLP is a fake OTLP endpoint that captures every export body. The OTLP +// HTTP exporter uses no compression by default, so metric names appear verbatim +// in the protobuf payload and can be matched with a substring search. +type fakeOTLP struct { + server *httptest.Server + mu sync.Mutex + bodies [][]byte +} + +func newOTLP(t *testing.T) *fakeOTLP { + t.Helper() + f := &fakeOTLP{} + f.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + f.mu.Lock() + f.bodies = append(f.bodies, body) + f.mu.Unlock() + w.WriteHeader(http.StatusOK) + })) + t.Cleanup(f.server.Close) + return f +} + +func (f *fakeOTLP) sawMetric(name string) bool { + f.mu.Lock() + defer f.mu.Unlock() + for _, b := range f.bodies { + if bytes.Contains(b, []byte(name)) { + return true + } + } + return false +} + +func (f *fakeOTLP) requireMetric(t *testing.T, name string) { + t.Helper() + require.Eventually(t, func() bool { + return f.sawMetric(name) + }, 3*time.Second, 20*time.Millisecond, "expected an OTLP export containing %q", name) +} + +func targetServer(t *testing.T, status int) *httptest.Server { + t.Helper() + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(status) + })) + t.Cleanup(server.Close) + return server +} + +func TestHTTPJob_RecordsOTelMetrics(t *testing.T) { + target := targetServer(t, http.StatusOK) + otlp := newOTLP(t) + + monitor := &v1.HTTPMonitor{ + Url: target.URL, + Method: "GET", + Timeout: 10000, + Retry: 1, + OtelConfig: &v1.OtelConfig{Endpoint: otlp.server.URL}, + } + + data, err := job.NewJobRunner().HTTPJob(context.Background(), monitor, "test-region") + require.NoError(t, err) + assert.Equal(t, "success", data.RequestStatus) + otlp.requireMetric(t, "openstatus.status") +} + +func TestHTTPJob_RecordsErrorOnNon2xx(t *testing.T) { + target := targetServer(t, http.StatusInternalServerError) + otlp := newOTLP(t) + + monitor := &v1.HTTPMonitor{ + Url: target.URL, + Method: "GET", + Timeout: 10000, + Retry: 2, + OtelConfig: &v1.OtelConfig{Endpoint: otlp.server.URL}, + } + + data, err := job.NewJobRunner().HTTPJob(context.Background(), monitor, "test-region") + require.NoError(t, err) + assert.Equal(t, uint8(1), data.Error) + otlp.requireMetric(t, "openstatus.error") + assert.False(t, otlp.sawMetric("openstatus.status"), "a non-2xx must not record the status counter") +} + +func TestHTTPJob_RecordsOTelOnFailure(t *testing.T) { + otlp := newOTLP(t) + + monitor := &v1.HTTPMonitor{ + Url: "http://127.0.0.1:1", + Method: "GET", + Timeout: 1000, + Retry: 1, + OtelConfig: &v1.OtelConfig{Endpoint: otlp.server.URL}, + } + + _, err := job.NewJobRunner().HTTPJob(context.Background(), monitor, "test-region") + require.Error(t, err) + otlp.requireMetric(t, "openstatus.error") +} + +func TestHTTPJob_NoOTelWhenEndpointEmpty(t *testing.T) { + target := targetServer(t, http.StatusOK) + + monitor := &v1.HTTPMonitor{ + Url: target.URL, + Method: "GET", + Timeout: 10000, + Retry: 1, + } + + data, err := job.NewJobRunner().HTTPJob(context.Background(), monitor, "test-region") + require.NoError(t, err) + assert.Equal(t, "success", data.RequestStatus) +} + +func TestTCPJob_RecordsOTelMetrics(t *testing.T) { + target := targetServer(t, http.StatusOK) + otlp := newOTLP(t) + + monitor := &v1.TCPMonitor{ + Uri: strings.TrimPrefix(target.URL, "http://"), + Timeout: 5, + Retry: 1, + OtelConfig: &v1.OtelConfig{Endpoint: otlp.server.URL}, + } + + data, err := job.NewJobRunner().TCPJob(context.Background(), monitor, "test-region") + require.NoError(t, err) + assert.Equal(t, "active", data.RequestStatus) + otlp.requireMetric(t, "openstatus.status") +} + +func TestTCPJob_RecordsOTelOnFailure(t *testing.T) { + otlp := newOTLP(t) + + monitor := &v1.TCPMonitor{ + Uri: "127.0.0.1:1", + Timeout: 1, + Retry: 1, + OtelConfig: &v1.OtelConfig{Endpoint: otlp.server.URL}, + } + + data, err := job.NewJobRunner().TCPJob(context.Background(), monitor, "test-region") + require.NoError(t, err) + assert.Equal(t, 1, data.Error) + otlp.requireMetric(t, "openstatus.error") +} diff --git a/apps/checker/pkg/job/tcp_job.go b/apps/checker/pkg/job/tcp_job.go index 5c549c19..ab8324a9 100644 --- a/apps/checker/pkg/job/tcp_job.go +++ b/apps/checker/pkg/job/tcp_job.go @@ -8,7 +8,9 @@ import ( "github.com/cenkalti/backoff/v5" "github.com/google/uuid" "github.com/openstatushq/openstatus/apps/checker/checker" + "github.com/openstatushq/openstatus/apps/checker/pkg/otel" v1 "github.com/openstatushq/openstatus/apps/checker/proto/private_location/v1" + "github.com/openstatushq/openstatus/apps/checker/request" ) // AssertionResult tracks the results of running assertions @@ -33,7 +35,7 @@ type TCPPrivateRegionData struct { // runAssertions performs all configured assertions for TCP and returns their results -func (jobRunner) TCPJob(ctx context.Context, monitor *v1.TCPMonitor) (*TCPPrivateRegionData, error) { +func (jobRunner) TCPJob(ctx context.Context, monitor *v1.TCPMonitor, region string) (*TCPPrivateRegionData, error) { retry := monitor.Retry if retry == 0 { retry = 3 @@ -44,7 +46,10 @@ func (jobRunner) TCPJob(ctx context.Context, monitor *v1.TCPMonitor) (*TCPPrivat degradedAfter = *monitor.DegradedAt } + req := tcpCheckerRequest(monitor) + var called int + var lastResult checker.TCPResponse op := func() (*TCPPrivateRegionData, error) { called++ @@ -59,6 +64,8 @@ func (jobRunner) TCPJob(ctx context.Context, monitor *v1.TCPMonitor) (*TCPPrivat return nil, fmt.Errorf("failed to generate UUID: %w", uuidErr) } + lastResult = checker.TCPResponse{Error: 1} + return &TCPPrivateRegionData{ ID: id.String(), Latency: 0, @@ -72,6 +79,7 @@ func (jobRunner) TCPJob(ctx context.Context, monitor *v1.TCPMonitor) (*TCPPrivat } latency := res.TCPDone - res.TCPStart + lastResult = checker.TCPResponse{Latency: latency, Timing: res} var requestStatus = "active" @@ -107,8 +115,33 @@ func (jobRunner) TCPJob(ctx context.Context, monitor *v1.TCPMonitor) (*TCPPrivat backoff.WithMaxTries(uint(retry)), backoff.WithBackOff(backoff.NewExponentialBackOff()), ) + + recordTCPOtel(ctx, req, lastResult, region, err != nil) + if err != nil { return nil, fmt.Errorf("TCP job failed after %d retries: %w", retry, err) } return resp, nil } + +func tcpCheckerRequest(monitor *v1.TCPMonitor) request.TCPCheckerRequest { + req := request.TCPCheckerRequest{URI: monitor.Uri} + if otelCfg := monitor.GetOtelConfig(); otelCfg.GetEndpoint() != "" { + req.OtelConfig.Endpoint = otelCfg.GetEndpoint() + req.OtelConfig.Headers = headersToMap(otelCfg.GetHeaders()) + } + + return req +} + +func recordTCPOtel(ctx context.Context, req request.TCPCheckerRequest, result checker.TCPResponse, region string, failed bool) { + if req.OtelConfig.Endpoint == "" { + return + } + + if failed { + result.Error = 1 + } + + otel.RecordTCPMetrics(ctx, req, result, region) +} diff --git a/apps/checker/pkg/job/tcp_job_test.go b/apps/checker/pkg/job/tcp_job_test.go index f77c4b8e..fb3f4db8 100644 --- a/apps/checker/pkg/job/tcp_job_test.go +++ b/apps/checker/pkg/job/tcp_job_test.go @@ -15,7 +15,7 @@ func TestTCPJob_Success(t *testing.T) { Timeout: 1, Retry: 1, } - data, err := job.NewJobRunner().TCPJob(context.Background(), monitor) + data, err := job.NewJobRunner().TCPJob(context.Background(), monitor, "test-region") if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -35,7 +35,7 @@ func TestTCPJob_Failure(t *testing.T) { Retry: 1, } - data, err := job.NewJobRunner().TCPJob(context.Background(), monitor) + data, err := job.NewJobRunner().TCPJob(context.Background(), monitor, "test-region") if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/apps/checker/pkg/scheduler/scheduler.go b/apps/checker/pkg/scheduler/scheduler.go index 396e1593..7b5e3860 100644 --- a/apps/checker/pkg/scheduler/scheduler.go +++ b/apps/checker/pkg/scheduler/scheduler.go @@ -57,7 +57,7 @@ func (mm *MonitorManager) UpdateMonitors(ctx context.Context) { monitor := m c := context.Background() log.Printf("Starting job for monitor %s (%s)", monitor.Id, monitor.Url) - data, err := mm.JobRunner.HTTPJob(c, monitor) + data, err := mm.JobRunner.HTTPJob(c, monitor, res.Msg.Region) if err != nil { log.Printf("Monitor check failed for %s (%s): %v", monitor.Id, monitor.Url, err) @@ -118,7 +118,7 @@ func (mm *MonitorManager) UpdateMonitors(ctx context.Context) { monitor := m c := context.Background() log.Printf("Starting TCP job for monitor %s (%s)", monitor.Id, monitor.Uri) - data, err := mm.JobRunner.TCPJob(c, monitor) + data, err := mm.JobRunner.TCPJob(c, monitor, res.Msg.Region) if err != nil { log.Printf("TCP monitor check failed for %s (%s): %v", monitor.Id, monitor.Uri, err) } diff --git a/apps/checker/pkg/scheduler/scheduler_test.go b/apps/checker/pkg/scheduler/scheduler_test.go index 7fdf8001..e70aeed2 100644 --- a/apps/checker/pkg/scheduler/scheduler_test.go +++ b/apps/checker/pkg/scheduler/scheduler_test.go @@ -21,23 +21,44 @@ type mockJobRunner struct { TCPJobCalled atomic.Bool DNSJobCalled atomic.Bool mu sync.Mutex + httpRegion string + tcpRegion string } -func (m *mockJobRunner) HTTPJob(ctx context.Context, monitor *v1.HTTPMonitor) (*job.HttpPrivateRegionData, error) { +func (m *mockJobRunner) HTTPJob(ctx context.Context, monitor *v1.HTTPMonitor, region string) (*job.HttpPrivateRegionData, error) { m.HTTPJobCalled.Store(true) + m.mu.Lock() + m.httpRegion = region + m.mu.Unlock() return &job.HttpPrivateRegionData{}, nil } -func (m *mockJobRunner) TCPJob(ctx context.Context, monitor *v1.TCPMonitor) (*job.TCPPrivateRegionData, error) { +func (m *mockJobRunner) TCPJob(ctx context.Context, monitor *v1.TCPMonitor, region string) (*job.TCPPrivateRegionData, error) { m.TCPJobCalled.Store(true) + m.mu.Lock() + m.tcpRegion = region + m.mu.Unlock() return &job.TCPPrivateRegionData{}, nil } +func (m *mockJobRunner) HTTPRegion() string { + m.mu.Lock() + defer m.mu.Unlock() + return m.httpRegion +} + +func (m *mockJobRunner) TCPRegion() string { + m.mu.Lock() + defer m.mu.Unlock() + return m.tcpRegion +} + func (m *mockJobRunner) DNSJob(ctx context.Context, monitor *v1.DNSMonitor) (*job.DNSPrivateRegionData, error) { m.TCPJobCalled.Store(true) return &job.DNSPrivateRegionData{}, nil } + // mockClient implements v1.PrivateLocationServiceClient for testing type mockClient struct { MonitorsFunc func(ctx context.Context, req *connect.Request[v1.MonitorsRequest]) (*connect.Response[v1.MonitorsResponse], error) @@ -64,6 +85,7 @@ func TestMonitorManager_StartAndStopJobs_WithJobRunner(t *testing.T) { httpMonitor := &v1.HTTPMonitor{Id: "http1", Url: "http://openstat.us", Periodicity: "10s"} tcpMonitor := &v1.TCPMonitor{Id: "tcp1", Uri: "openstatus:80", Periodicity: "10s"} + const wantRegion = "frankfurt-dc1" // Regression guard: the tasks scheduler never populates TaskContext.Context, // so jobs must pass a non-nil context to the connect client or IngestTCP panics. @@ -74,6 +96,7 @@ func TestMonitorManager_StartAndStopJobs_WithJobRunner(t *testing.T) { return connect.NewResponse(&v1.MonitorsResponse{ HttpMonitors: []*v1.HTTPMonitor{httpMonitor}, TcpMonitors: []*v1.TCPMonitor{tcpMonitor}, + Region: wantRegion, }), nil }, IngestHTTPFunc: func(ctx context.Context, req *connect.Request[v1.IngestHTTPRequest]) (*connect.Response[v1.IngestHTTPResponse], error) { @@ -85,7 +108,6 @@ func TestMonitorManager_StartAndStopJobs_WithJobRunner(t *testing.T) { } return connect.NewResponse(&v1.IngestTCPResponse{}), nil }, - } jobRunner := &mockJobRunner{} @@ -108,6 +130,12 @@ func TestMonitorManager_StartAndStopJobs_WithJobRunner(t *testing.T) { if !jobRunner.TCPJobCalled.Load() == true { t.Errorf("expected TCPJob to be called") } + if got := jobRunner.HTTPRegion(); got != wantRegion { + t.Errorf("expected HTTPJob to receive region %q, got %q", wantRegion, got) + } + if got := jobRunner.TCPRegion(); got != wantRegion { + t.Errorf("expected TCPJob to receive region %q, got %q", wantRegion, got) + } if !tcpIngestCtxNonNil.Load() { t.Errorf("expected IngestTCP to receive a non-nil context") } diff --git a/apps/checker/proto/private_location/v1/http_monitor.pb.go b/apps/checker/proto/private_location/v1/http_monitor.pb.go index e3527d5b..8148ffbe 100644 --- a/apps/checker/proto/private_location/v1/http_monitor.pb.go +++ b/apps/checker/proto/private_location/v1/http_monitor.pb.go @@ -21,58 +21,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -type Headers struct { - state protoimpl.MessageState `protogen:"open.v1"` - Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` - Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Headers) Reset() { - *x = Headers{} - mi := &file_private_location_v1_http_monitor_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Headers) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Headers) ProtoMessage() {} - -func (x *Headers) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_http_monitor_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Headers.ProtoReflect.Descriptor instead. -func (*Headers) Descriptor() ([]byte, []int) { - return file_private_location_v1_http_monitor_proto_rawDescGZIP(), []int{0} -} - -func (x *Headers) GetKey() string { - if x != nil { - return x.Key - } - return "" -} - -func (x *Headers) GetValue() string { - if x != nil { - return x.Value - } - return "" -} - type HTTPMonitor struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` @@ -88,13 +36,14 @@ type HTTPMonitor struct { StatusCodeAssertions []*StatusCodeAssertion `protobuf:"bytes,11,rep,name=status_code_assertions,json=statusCodeAssertions,proto3" json:"status_code_assertions,omitempty"` BodyAssertions []*BodyAssertion `protobuf:"bytes,12,rep,name=body_assertions,json=bodyAssertions,proto3" json:"body_assertions,omitempty"` HeaderAssertions []*HeaderAssertion `protobuf:"bytes,13,rep,name=header_assertions,json=headerAssertions,proto3" json:"header_assertions,omitempty"` + OtelConfig *OtelConfig `protobuf:"bytes,20,opt,name=otel_config,json=otelConfig,proto3" json:"otel_config,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *HTTPMonitor) Reset() { *x = HTTPMonitor{} - mi := &file_private_location_v1_http_monitor_proto_msgTypes[1] + mi := &file_private_location_v1_http_monitor_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -106,7 +55,7 @@ func (x *HTTPMonitor) String() string { func (*HTTPMonitor) ProtoMessage() {} func (x *HTTPMonitor) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_http_monitor_proto_msgTypes[1] + mi := &file_private_location_v1_http_monitor_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -119,7 +68,7 @@ func (x *HTTPMonitor) ProtoReflect() protoreflect.Message { // Deprecated: Use HTTPMonitor.ProtoReflect.Descriptor instead. func (*HTTPMonitor) Descriptor() ([]byte, []int) { - return file_private_location_v1_http_monitor_proto_rawDescGZIP(), []int{1} + return file_private_location_v1_http_monitor_proto_rawDescGZIP(), []int{0} } func (x *HTTPMonitor) GetId() string { @@ -213,14 +162,18 @@ func (x *HTTPMonitor) GetHeaderAssertions() []*HeaderAssertion { return nil } +func (x *HTTPMonitor) GetOtelConfig() *OtelConfig { + if x != nil { + return x.OtelConfig + } + return nil +} + var File_private_location_v1_http_monitor_proto protoreflect.FileDescriptor const file_private_location_v1_http_monitor_proto_rawDesc = "" + "\n" + - "&private_location/v1/http_monitor.proto\x12\x13private_location.v1\x1a$private_location/v1/assertions.proto\"1\n" + - "\aHeaders\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value\"\xc6\x04\n" + + "&private_location/v1/http_monitor.proto\x12\x13private_location.v1\x1a$private_location/v1/assertions.proto\x1a\x1eprivate_location/v1/otel.proto\"\x88\x05\n" + "\vHTTPMonitor\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x10\n" + "\x03url\x18\x02 \x01(\tR\x03url\x12 \n" + @@ -236,7 +189,9 @@ const file_private_location_v1_http_monitor_proto_rawDesc = "" + " \x03(\v2\x1c.private_location.v1.HeadersR\aheaders\x12^\n" + "\x16status_code_assertions\x18\v \x03(\v2(.private_location.v1.StatusCodeAssertionR\x14statusCodeAssertions\x12K\n" + "\x0fbody_assertions\x18\f \x03(\v2\".private_location.v1.BodyAssertionR\x0ebodyAssertions\x12Q\n" + - "\x11header_assertions\x18\r \x03(\v2$.private_location.v1.HeaderAssertionR\x10headerAssertionsB\x0e\n" + + "\x11header_assertions\x18\r \x03(\v2$.private_location.v1.HeaderAssertionR\x10headerAssertions\x12@\n" + + "\votel_config\x18\x14 \x01(\v2\x1f.private_location.v1.OtelConfigR\n" + + "otelConfigB\x0e\n" + "\f_degraded_atBJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" var ( @@ -251,24 +206,26 @@ func file_private_location_v1_http_monitor_proto_rawDescGZIP() []byte { return file_private_location_v1_http_monitor_proto_rawDescData } -var file_private_location_v1_http_monitor_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_private_location_v1_http_monitor_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_private_location_v1_http_monitor_proto_goTypes = []any{ - (*Headers)(nil), // 0: private_location.v1.Headers - (*HTTPMonitor)(nil), // 1: private_location.v1.HTTPMonitor + (*HTTPMonitor)(nil), // 0: private_location.v1.HTTPMonitor + (*Headers)(nil), // 1: private_location.v1.Headers (*StatusCodeAssertion)(nil), // 2: private_location.v1.StatusCodeAssertion (*BodyAssertion)(nil), // 3: private_location.v1.BodyAssertion (*HeaderAssertion)(nil), // 4: private_location.v1.HeaderAssertion + (*OtelConfig)(nil), // 5: private_location.v1.OtelConfig } var file_private_location_v1_http_monitor_proto_depIdxs = []int32{ - 0, // 0: private_location.v1.HTTPMonitor.headers:type_name -> private_location.v1.Headers + 1, // 0: private_location.v1.HTTPMonitor.headers:type_name -> private_location.v1.Headers 2, // 1: private_location.v1.HTTPMonitor.status_code_assertions:type_name -> private_location.v1.StatusCodeAssertion 3, // 2: private_location.v1.HTTPMonitor.body_assertions:type_name -> private_location.v1.BodyAssertion 4, // 3: private_location.v1.HTTPMonitor.header_assertions:type_name -> private_location.v1.HeaderAssertion - 4, // [4:4] is the sub-list for method output_type - 4, // [4:4] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 5, // 4: private_location.v1.HTTPMonitor.otel_config:type_name -> private_location.v1.OtelConfig + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name } func init() { file_private_location_v1_http_monitor_proto_init() } @@ -277,14 +234,15 @@ func file_private_location_v1_http_monitor_proto_init() { return } file_private_location_v1_assertions_proto_init() - file_private_location_v1_http_monitor_proto_msgTypes[1].OneofWrappers = []any{} + file_private_location_v1_otel_proto_init() + file_private_location_v1_http_monitor_proto_msgTypes[0].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_http_monitor_proto_rawDesc), len(file_private_location_v1_http_monitor_proto_rawDesc)), NumEnums: 0, - NumMessages: 2, + NumMessages: 1, NumExtensions: 0, NumServices: 0, }, diff --git a/apps/checker/proto/private_location/v1/otel.pb.go b/apps/checker/proto/private_location/v1/otel.pb.go new file mode 100644 index 00000000..4a819edb --- /dev/null +++ b/apps/checker/proto/private_location/v1/otel.pb.go @@ -0,0 +1,189 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: private_location/v1/otel.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Headers struct { + state protoimpl.MessageState `protogen:"open.v1"` + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Headers) Reset() { + *x = Headers{} + mi := &file_private_location_v1_otel_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Headers) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Headers) ProtoMessage() {} + +func (x *Headers) ProtoReflect() protoreflect.Message { + mi := &file_private_location_v1_otel_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Headers.ProtoReflect.Descriptor instead. +func (*Headers) Descriptor() ([]byte, []int) { + return file_private_location_v1_otel_proto_rawDescGZIP(), []int{0} +} + +func (x *Headers) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *Headers) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +type OtelConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Endpoint string `protobuf:"bytes,1,opt,name=endpoint,proto3" json:"endpoint,omitempty"` + Headers []*Headers `protobuf:"bytes,2,rep,name=headers,proto3" json:"headers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OtelConfig) Reset() { + *x = OtelConfig{} + mi := &file_private_location_v1_otel_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OtelConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OtelConfig) ProtoMessage() {} + +func (x *OtelConfig) ProtoReflect() protoreflect.Message { + mi := &file_private_location_v1_otel_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OtelConfig.ProtoReflect.Descriptor instead. +func (*OtelConfig) Descriptor() ([]byte, []int) { + return file_private_location_v1_otel_proto_rawDescGZIP(), []int{1} +} + +func (x *OtelConfig) GetEndpoint() string { + if x != nil { + return x.Endpoint + } + return "" +} + +func (x *OtelConfig) GetHeaders() []*Headers { + if x != nil { + return x.Headers + } + return nil +} + +var File_private_location_v1_otel_proto protoreflect.FileDescriptor + +const file_private_location_v1_otel_proto_rawDesc = "" + + "\n" + + "\x1eprivate_location/v1/otel.proto\x12\x13private_location.v1\"1\n" + + "\aHeaders\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value\"`\n" + + "\n" + + "OtelConfig\x12\x1a\n" + + "\bendpoint\x18\x01 \x01(\tR\bendpoint\x126\n" + + "\aheaders\x18\x02 \x03(\v2\x1c.private_location.v1.HeadersR\aheadersBJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" + +var ( + file_private_location_v1_otel_proto_rawDescOnce sync.Once + file_private_location_v1_otel_proto_rawDescData []byte +) + +func file_private_location_v1_otel_proto_rawDescGZIP() []byte { + file_private_location_v1_otel_proto_rawDescOnce.Do(func() { + file_private_location_v1_otel_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_private_location_v1_otel_proto_rawDesc), len(file_private_location_v1_otel_proto_rawDesc))) + }) + return file_private_location_v1_otel_proto_rawDescData +} + +var file_private_location_v1_otel_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_private_location_v1_otel_proto_goTypes = []any{ + (*Headers)(nil), // 0: private_location.v1.Headers + (*OtelConfig)(nil), // 1: private_location.v1.OtelConfig +} +var file_private_location_v1_otel_proto_depIdxs = []int32{ + 0, // 0: private_location.v1.OtelConfig.headers:type_name -> private_location.v1.Headers + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_private_location_v1_otel_proto_init() } +func file_private_location_v1_otel_proto_init() { + if File_private_location_v1_otel_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_otel_proto_rawDesc), len(file_private_location_v1_otel_proto_rawDesc)), + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_private_location_v1_otel_proto_goTypes, + DependencyIndexes: file_private_location_v1_otel_proto_depIdxs, + MessageInfos: file_private_location_v1_otel_proto_msgTypes, + }.Build() + File_private_location_v1_otel_proto = out.File + file_private_location_v1_otel_proto_goTypes = nil + file_private_location_v1_otel_proto_depIdxs = nil +} diff --git a/apps/checker/proto/private_location/v1/private_location.pb.go b/apps/checker/proto/private_location/v1/private_location.pb.go index 71a8399a..e31eb986 100644 --- a/apps/checker/proto/private_location/v1/private_location.pb.go +++ b/apps/checker/proto/private_location/v1/private_location.pb.go @@ -9,7 +9,6 @@ package v1 import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" - _ "google.golang.org/protobuf/types/known/structpb" reflect "reflect" sync "sync" unsafe "unsafe" @@ -63,6 +62,7 @@ type MonitorsResponse struct { HttpMonitors []*HTTPMonitor `protobuf:"bytes,1,rep,name=http_monitors,json=httpMonitors,proto3" json:"http_monitors,omitempty"` TcpMonitors []*TCPMonitor `protobuf:"bytes,2,rep,name=tcp_monitors,json=tcpMonitors,proto3" json:"tcp_monitors,omitempty"` DnsMonitors []*DNSMonitor `protobuf:"bytes,3,rep,name=dns_monitors,json=dnsMonitors,proto3" json:"dns_monitors,omitempty"` + Region string `protobuf:"bytes,4,opt,name=region,proto3" json:"region,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -118,6 +118,13 @@ func (x *MonitorsResponse) GetDnsMonitors() []*DNSMonitor { return nil } +func (x *MonitorsResponse) GetRegion() string { + if x != nil { + return x.Region + } + return "" +} + type IngestTCPRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` @@ -654,12 +661,13 @@ var File_private_location_v1_private_location_proto protoreflect.FileDescriptor const file_private_location_v1_private_location_proto_rawDesc = "" + "\n" + - "*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" + - "\x0fMonitorsRequest\"\xe1\x01\n" + + "*private_location/v1/private_location.proto\x12\x13private_location.v1\x1a%private_location/v1/dns_monitor.proto\x1a&private_location/v1/http_monitor.proto\x1a%private_location/v1/tcp_monitor.proto\"\x11\n" + + "\x0fMonitorsRequest\"\xf9\x01\n" + "\x10MonitorsResponse\x12E\n" + "\rhttp_monitors\x18\x01 \x03(\v2 .private_location.v1.HTTPMonitorR\fhttpMonitors\x12B\n" + "\ftcp_monitors\x18\x02 \x03(\v2\x1f.private_location.v1.TCPMonitorR\vtcpMonitors\x12B\n" + - "\fdns_monitors\x18\x03 \x03(\v2\x1f.private_location.v1.DNSMonitorR\vdnsMonitors\"\x9e\x02\n" + + "\fdns_monitors\x18\x03 \x03(\v2\x1f.private_location.v1.DNSMonitorR\vdnsMonitors\x12\x16\n" + + "\x06region\x18\x04 \x01(\tR\x06region\"\x9e\x02\n" + "\x10IngestTCPRequest\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + diff --git a/apps/checker/proto/private_location/v1/tcp_monitor.pb.go b/apps/checker/proto/private_location/v1/tcp_monitor.pb.go index 3cc2ba3a..e67daf94 100644 --- a/apps/checker/proto/private_location/v1/tcp_monitor.pb.go +++ b/apps/checker/proto/private_location/v1/tcp_monitor.pb.go @@ -29,6 +29,7 @@ type TCPMonitor struct { DegradedAt *int64 `protobuf:"varint,4,opt,name=degraded_at,json=degradedAt,proto3,oneof" json:"degraded_at,omitempty"` Periodicity string `protobuf:"bytes,5,opt,name=periodicity,proto3" json:"periodicity,omitempty"` Retry int64 `protobuf:"varint,6,opt,name=retry,proto3" json:"retry,omitempty"` + OtelConfig *OtelConfig `protobuf:"bytes,20,opt,name=otel_config,json=otelConfig,proto3" json:"otel_config,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -105,11 +106,18 @@ func (x *TCPMonitor) GetRetry() int64 { return 0 } +func (x *TCPMonitor) GetOtelConfig() *OtelConfig { + if x != nil { + return x.OtelConfig + } + return nil +} + var File_private_location_v1_tcp_monitor_proto protoreflect.FileDescriptor const file_private_location_v1_tcp_monitor_proto_rawDesc = "" + "\n" + - "%private_location/v1/tcp_monitor.proto\x12\x13private_location.v1\"\xb6\x01\n" + + "%private_location/v1/tcp_monitor.proto\x12\x13private_location.v1\x1a\x1eprivate_location/v1/otel.proto\"\xf8\x01\n" + "\n" + "TCPMonitor\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x10\n" + @@ -118,7 +126,9 @@ const file_private_location_v1_tcp_monitor_proto_rawDesc = "" + "\vdegraded_at\x18\x04 \x01(\x03H\x00R\n" + "degradedAt\x88\x01\x01\x12 \n" + "\vperiodicity\x18\x05 \x01(\tR\vperiodicity\x12\x14\n" + - "\x05retry\x18\x06 \x01(\x03R\x05retryB\x0e\n" + + "\x05retry\x18\x06 \x01(\x03R\x05retry\x12@\n" + + "\votel_config\x18\x14 \x01(\v2\x1f.private_location.v1.OtelConfigR\n" + + "otelConfigB\x0e\n" + "\f_degraded_atBJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" var ( @@ -136,13 +146,15 @@ func file_private_location_v1_tcp_monitor_proto_rawDescGZIP() []byte { var file_private_location_v1_tcp_monitor_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_private_location_v1_tcp_monitor_proto_goTypes = []any{ (*TCPMonitor)(nil), // 0: private_location.v1.TCPMonitor + (*OtelConfig)(nil), // 1: private_location.v1.OtelConfig } var file_private_location_v1_tcp_monitor_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 1, // 0: private_location.v1.TCPMonitor.otel_config:type_name -> private_location.v1.OtelConfig + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name } func init() { file_private_location_v1_tcp_monitor_proto_init() } @@ -150,6 +162,7 @@ func file_private_location_v1_tcp_monitor_proto_init() { if File_private_location_v1_tcp_monitor_proto != nil { return } + file_private_location_v1_otel_proto_init() file_private_location_v1_tcp_monitor_proto_msgTypes[0].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ diff --git a/apps/private-location/internal/database/models.go b/apps/private-location/internal/database/models.go index aa6f3ebf..037a2b4f 100644 --- a/apps/private-location/internal/database/models.go +++ b/apps/private-location/internal/database/models.go @@ -41,5 +41,6 @@ type Monitor struct { } type PrivateLocation struct { - ID int `db:"id"` + ID int `db:"id"` + Name string `db:"name"` } diff --git a/apps/private-location/internal/server/db_testdata b/apps/private-location/internal/server/db_testdata index 85d2e2e1..57366385 100644 --- a/apps/private-location/internal/server/db_testdata +++ b/apps/private-location/internal/server/db_testdata @@ -436,7 +436,7 @@ INSERT INTO "monitor" ("id", "job_type", "periodicity", "active", "url", "name", ('2', 'http', '10m', '0', 'https://www.google.com', '', '', '1', '', '', 'GET', '1760358329', 'gru', '1760358329', 'active', NULL, NULL, '1', '45000', NULL, NULL, NULL, '3', '1'), ('3', 'http', '1m', '1', 'https://www.openstatus.dev', 'OpenStatus', 'OpenStatus website', '1', '[{"key":"key", "value":"value"}]', '{"hello":"world"}', 'GET', '1760358329', 'ams', '1760358329', 'active', NULL, NULL, '0', '45000', NULL, NULL, NULL, '3', '1'), ('4', 'http', '10m', '1', 'https://www.google.com', '', '', '1', '', '', 'GET', '1760358329', 'gru', '1760358329', 'active', NULL, NULL, '1', '45000', NULL, 'https://otel.com:4337', '[{"key":"Authorization","value":"Basic"}]', '3', '1'), -('5', 'http', '10m', '1', 'https://openstat.us', '', '', '3', '', '', 'GET', '1760358329', 'ams', '1760358329', 'active', NULL, NULL, '1', '45000', NULL, NULL, NULL, '3', '1'), +('5', 'http', '10m', '1', 'https://openstat.us', '', '', '3', '', '', 'GET', '1760358329', 'ams', '1760358329', 'active', NULL, NULL, '1', '45000', NULL, 'https://otel.example.com:4318', '[{"key":"Authorization","value":"Bearer token"}]', '3', '1'), ('6', 'tcp', '5m', '1', 'tcp://db.example.com:5432', 'Database TCP', 'Database TCP check', '3', '', '', '', '1760358329', 'ams', '1760358329', 'active', NULL, NULL, '0', '30000', '5000', NULL, NULL, '2', '0'), ('7', 'dns', '5m', '1', 'openstatus.dev', 'DNS Check', 'DNS check for openstatus.dev', '3', '', '', '', '1760358329', 'ams', '1760358329', 'active', '[{"version":"v1","type":"dnsRecord","key":"A","compare":"contains","target":"76.76.21.21"}]', NULL, '0', '30000', '3000', NULL, NULL, '2', '0'); diff --git a/apps/private-location/internal/server/monitors.go b/apps/private-location/internal/server/monitors.go index 5a2cee08..b80badaf 100644 --- a/apps/private-location/internal/server/monitors.go +++ b/apps/private-location/internal/server/monitors.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "encoding/json" + "errors" "strconv" "connectrpc.com/connect" @@ -179,11 +180,43 @@ func (h *privateLocationHandler) Monitors(ctx context.Context, req *connect.Requ return nil, connect.NewError(connect.CodeUnauthenticated, ErrMissingToken) } + var location database.PrivateLocation + if err := h.db.Get(&location, "SELECT id, name FROM private_location WHERE token = ?", token); err != nil && !errors.Is(err, sql.ErrNoRows) { + return nil, connect.NewError(connect.CodeInternal, err) + } + var monitors []database.Monitor - err := h.db.Select(&monitors, "SELECT monitor.id, monitor.job_type, monitor.url, monitor.periodicity, monitor.method, monitor.body, monitor.timeout, monitor.degraded_after, monitor.follow_redirects, monitor.headers, monitor.assertions, monitor.workspace_id, monitor.retry 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.active = 1", token) + err := h.db.Select(&monitors, "SELECT monitor.id, monitor.job_type, monitor.url, monitor.periodicity, monitor.method, monitor.body, monitor.timeout, monitor.degraded_after, monitor.follow_redirects, monitor.headers, monitor.assertions, monitor.workspace_id, monitor.retry, monitor.otel_endpoint, monitor.otel_headers 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.active = 1", token) if err != nil { return nil, connect.NewError(connect.CodeInternal, err) } + httpMonitors, tcpMonitors, dnsMonitors, workspaceId := mapMonitors(ctx, monitors) + + // Enrich wide event with monitor counts + if holder := GetEvent(ctx); holder != nil { + holder.Event["private_location"] = map[string]any{ + "workspace_id": workspaceId, + "http_monitors": len(httpMonitors), + "tcp_monitors": len(tcpMonitors), + "dns_monitors": len(dnsMonitors), + "total_monitors": len(monitors), + } + } + + return connect.NewResponse(&private_locationv1.MonitorsResponse{ + HttpMonitors: httpMonitors, + TcpMonitors: tcpMonitors, + DnsMonitors: dnsMonitors, + Region: location.Name, + }), nil +} + +func mapMonitors(ctx context.Context, monitors []database.Monitor) ( + []*private_locationv1.HTTPMonitor, + []*private_locationv1.TCPMonitor, + []*private_locationv1.DNSMonitor, + int, +) { var workspaceId int var httpMonitors []*private_locationv1.HTTPMonitor var tcpMonitors []*private_locationv1.TCPMonitor @@ -195,68 +228,93 @@ func (h *privateLocationHandler) Monitors(ctx context.Context, req *connect.Requ switch monitor.JobType { case database.JobTypeHTTP: - var headers []*private_locationv1.Headers - if err := json.Unmarshal([]byte(monitor.Headers), &headers); err != nil { - addParseError(ctx, "headers_unmarshal", err) - headers = nil - } + httpMonitors = append(httpMonitors, toHTTPMonitor(ctx, monitor)) + case database.JobTypeTCP: + tcpMonitors = append(tcpMonitors, toTCPMonitor(ctx, monitor)) + case database.JobTypeDNS: + dnsMonitors = append(dnsMonitors, toDNSMonitor(ctx, monitor)) + } + } - statusAssertions, headerAssertions, bodyAssertions := ParseAssertions(ctx, monitor.Assertions) + return httpMonitors, tcpMonitors, dnsMonitors, workspaceId +} - httpMonitors = append(httpMonitors, &private_locationv1.HTTPMonitor{ - Url: monitor.URL, - Periodicity: monitor.Periodicity, - Id: strconv.Itoa(monitor.ID), - Method: monitor.Method, - Body: monitor.Body, - Timeout: monitor.Timeout, - DegradedAt: &monitor.DegradedAfter.Int64, - Retry: int64(monitor.Retry), - FollowRedirects: monitor.FollowRedirects, - Headers: headers, - StatusCodeAssertions: statusAssertions, - HeaderAssertions: headerAssertions, - BodyAssertions: bodyAssertions, - }) +func toHTTPMonitor(ctx context.Context, monitor database.Monitor) *private_locationv1.HTTPMonitor { + var headers []*private_locationv1.Headers + if err := json.Unmarshal([]byte(monitor.Headers), &headers); err != nil { + addParseError(ctx, "headers_unmarshal", err) + headers = nil + } - case database.JobTypeTCP: - tcpMonitors = append(tcpMonitors, &private_locationv1.TCPMonitor{ - Id: strconv.Itoa(monitor.ID), - Uri: monitor.URL, - Timeout: monitor.Timeout, - DegradedAt: &monitor.DegradedAfter.Int64, - Periodicity: monitor.Periodicity, - Retry: int64(monitor.Retry), - }) + statusAssertions, headerAssertions, bodyAssertions := ParseAssertions(ctx, monitor.Assertions) - case database.JobTypeDNS: - recordAssertions := ParseRecordAssertions(ctx, monitor.Assertions) - dnsMonitors = append(dnsMonitors, &private_locationv1.DNSMonitor{ - Id: strconv.Itoa(monitor.ID), - Uri: monitor.URL, - Timeout: monitor.Timeout, - DegradedAt: &monitor.DegradedAfter.Int64, - Periodicity: monitor.Periodicity, - Retry: int64(monitor.Retry), - RecordAssertions: recordAssertions, - }) - } + return &private_locationv1.HTTPMonitor{ + Url: monitor.URL, + Periodicity: monitor.Periodicity, + Id: strconv.Itoa(monitor.ID), + Method: monitor.Method, + Body: monitor.Body, + Timeout: monitor.Timeout, + DegradedAt: &monitor.DegradedAfter.Int64, + Retry: int64(monitor.Retry), + FollowRedirects: monitor.FollowRedirects, + Headers: headers, + StatusCodeAssertions: statusAssertions, + HeaderAssertions: headerAssertions, + BodyAssertions: bodyAssertions, + OtelConfig: buildOtelConfig(ctx, monitor), } +} - // Enrich wide event with monitor counts - if holder := GetEvent(ctx); holder != nil { - holder.Event["private_location"] = map[string]any{ - "workspace_id": workspaceId, - "http_monitors": len(httpMonitors), - "tcp_monitors": len(tcpMonitors), - "dns_monitors": len(dnsMonitors), - "total_monitors": len(monitors), - } +func toTCPMonitor(ctx context.Context, monitor database.Monitor) *private_locationv1.TCPMonitor { + return &private_locationv1.TCPMonitor{ + Id: strconv.Itoa(monitor.ID), + Uri: monitor.URL, + Timeout: monitor.Timeout, + DegradedAt: &monitor.DegradedAfter.Int64, + Periodicity: monitor.Periodicity, + Retry: int64(monitor.Retry), + OtelConfig: buildOtelConfig(ctx, monitor), } +} - return connect.NewResponse(&private_locationv1.MonitorsResponse{ - HttpMonitors: httpMonitors, - TcpMonitors: tcpMonitors, - DnsMonitors: dnsMonitors, - }), nil +func toDNSMonitor(ctx context.Context, monitor database.Monitor) *private_locationv1.DNSMonitor { + return &private_locationv1.DNSMonitor{ + Id: strconv.Itoa(monitor.ID), + Uri: monitor.URL, + Timeout: monitor.Timeout, + DegradedAt: &monitor.DegradedAfter.Int64, + Periodicity: monitor.Periodicity, + Retry: int64(monitor.Retry), + RecordAssertions: ParseRecordAssertions(ctx, monitor.Assertions), + } +} + +// buildOtelConfig maps a monitor's stored OTel settings to the proto config, +// returning nil when no endpoint is configured so the checker skips OTel. +func buildOtelConfig(ctx context.Context, monitor database.Monitor) *private_locationv1.OtelConfig { + if !monitor.OtelEndpoint.Valid || monitor.OtelEndpoint.String == "" { + return nil + } + + return &private_locationv1.OtelConfig{ + Endpoint: monitor.OtelEndpoint.String, + Headers: ParseOtelHeaders(ctx, monitor.OtelHeaders), + } +} + +// ParseOtelHeaders decodes the stored otel_headers JSON array ([]{key,value}), +// returning nil for a null/empty/invalid value. +func ParseOtelHeaders(ctx context.Context, raw sql.NullString) []*private_locationv1.Headers { + if !raw.Valid || raw.String == "" { + return nil + } + + var headers []*private_locationv1.Headers + if err := json.Unmarshal([]byte(raw.String), &headers); err != nil { + addParseError(ctx, "otel_headers_unmarshal", err) + return nil + } + + return headers } diff --git a/apps/private-location/internal/server/otel_test.go b/apps/private-location/internal/server/otel_test.go new file mode 100644 index 00000000..601123f9 --- /dev/null +++ b/apps/private-location/internal/server/otel_test.go @@ -0,0 +1,85 @@ +package server_test + +import ( + "context" + "database/sql" + "testing" + + "connectrpc.com/connect" + "github.com/openstatushq/openstatus/apps/private-location/internal/server" + private_locationv1 "github.com/openstatushq/openstatus/apps/private-location/proto/private_location/v1" +) + +func monitorsResponse(t *testing.T) *private_locationv1.MonitorsResponse { + t.Helper() + h := server.NewPrivateLocationServer(testDB(), getTBClient(context.Background())) + + req := connect.NewRequest(&private_locationv1.MonitorsRequest{}) + req.Header().Set("openstatus-token", "my-secret-key") + + resp, err := h.Monitors(context.Background(), req) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + return resp.Msg +} + +func TestMonitors_Region(t *testing.T) { + msg := monitorsResponse(t) + if msg.Region != "My Home" { + t.Errorf("expected Region 'My Home', got '%s'", msg.Region) + } +} + +func TestMonitors_HTTPMonitorOtelConfig(t *testing.T) { + msg := monitorsResponse(t) + if len(msg.HttpMonitors) != 1 { + t.Fatalf("expected 1 HTTP monitor, got %d", len(msg.HttpMonitors)) + } + + otel := msg.HttpMonitors[0].OtelConfig + if otel == nil { + t.Fatalf("expected OtelConfig, got nil") + } + if otel.Endpoint != "https://otel.example.com:4318" { + t.Errorf("expected endpoint 'https://otel.example.com:4318', got '%s'", otel.Endpoint) + } + if len(otel.Headers) != 1 { + t.Fatalf("expected 1 otel header, got %d", len(otel.Headers)) + } + if otel.Headers[0].Key != "Authorization" || otel.Headers[0].Value != "Bearer token" { + t.Errorf("unexpected otel header: %+v", otel.Headers[0]) + } +} + +func TestMonitors_TCPMonitorNoOtelConfig(t *testing.T) { + msg := monitorsResponse(t) + if len(msg.TcpMonitors) != 1 { + t.Fatalf("expected 1 TCP monitor, got %d", len(msg.TcpMonitors)) + } + if msg.TcpMonitors[0].OtelConfig != nil { + t.Errorf("expected nil OtelConfig for monitor without otel_endpoint, got %+v", msg.TcpMonitors[0].OtelConfig) + } +} + +func TestParseOtelHeaders(t *testing.T) { + tests := []struct { + name string + raw sql.NullString + want int + }{ + {"valid", sql.NullString{String: `[{"key":"A","value":"1"},{"key":"B","value":"2"}]`, Valid: true}, 2}, + {"null", sql.NullString{Valid: false}, 0}, + {"empty", sql.NullString{String: "", Valid: true}, 0}, + {"invalid", sql.NullString{String: "not json", Valid: true}, 0}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := server.ParseOtelHeaders(context.Background(), tt.raw) + if len(got) != tt.want { + t.Errorf("expected %d headers, got %d", tt.want, len(got)) + } + }) + } +} diff --git a/apps/private-location/proto/private_location/v1/http_monitor.pb.go b/apps/private-location/proto/private_location/v1/http_monitor.pb.go index e3527d5b..8148ffbe 100644 --- a/apps/private-location/proto/private_location/v1/http_monitor.pb.go +++ b/apps/private-location/proto/private_location/v1/http_monitor.pb.go @@ -21,58 +21,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -type Headers struct { - state protoimpl.MessageState `protogen:"open.v1"` - Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` - Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Headers) Reset() { - *x = Headers{} - mi := &file_private_location_v1_http_monitor_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Headers) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Headers) ProtoMessage() {} - -func (x *Headers) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_http_monitor_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Headers.ProtoReflect.Descriptor instead. -func (*Headers) Descriptor() ([]byte, []int) { - return file_private_location_v1_http_monitor_proto_rawDescGZIP(), []int{0} -} - -func (x *Headers) GetKey() string { - if x != nil { - return x.Key - } - return "" -} - -func (x *Headers) GetValue() string { - if x != nil { - return x.Value - } - return "" -} - type HTTPMonitor struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` @@ -88,13 +36,14 @@ type HTTPMonitor struct { StatusCodeAssertions []*StatusCodeAssertion `protobuf:"bytes,11,rep,name=status_code_assertions,json=statusCodeAssertions,proto3" json:"status_code_assertions,omitempty"` BodyAssertions []*BodyAssertion `protobuf:"bytes,12,rep,name=body_assertions,json=bodyAssertions,proto3" json:"body_assertions,omitempty"` HeaderAssertions []*HeaderAssertion `protobuf:"bytes,13,rep,name=header_assertions,json=headerAssertions,proto3" json:"header_assertions,omitempty"` + OtelConfig *OtelConfig `protobuf:"bytes,20,opt,name=otel_config,json=otelConfig,proto3" json:"otel_config,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *HTTPMonitor) Reset() { *x = HTTPMonitor{} - mi := &file_private_location_v1_http_monitor_proto_msgTypes[1] + mi := &file_private_location_v1_http_monitor_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -106,7 +55,7 @@ func (x *HTTPMonitor) String() string { func (*HTTPMonitor) ProtoMessage() {} func (x *HTTPMonitor) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_http_monitor_proto_msgTypes[1] + mi := &file_private_location_v1_http_monitor_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -119,7 +68,7 @@ func (x *HTTPMonitor) ProtoReflect() protoreflect.Message { // Deprecated: Use HTTPMonitor.ProtoReflect.Descriptor instead. func (*HTTPMonitor) Descriptor() ([]byte, []int) { - return file_private_location_v1_http_monitor_proto_rawDescGZIP(), []int{1} + return file_private_location_v1_http_monitor_proto_rawDescGZIP(), []int{0} } func (x *HTTPMonitor) GetId() string { @@ -213,14 +162,18 @@ func (x *HTTPMonitor) GetHeaderAssertions() []*HeaderAssertion { return nil } +func (x *HTTPMonitor) GetOtelConfig() *OtelConfig { + if x != nil { + return x.OtelConfig + } + return nil +} + var File_private_location_v1_http_monitor_proto protoreflect.FileDescriptor const file_private_location_v1_http_monitor_proto_rawDesc = "" + "\n" + - "&private_location/v1/http_monitor.proto\x12\x13private_location.v1\x1a$private_location/v1/assertions.proto\"1\n" + - "\aHeaders\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value\"\xc6\x04\n" + + "&private_location/v1/http_monitor.proto\x12\x13private_location.v1\x1a$private_location/v1/assertions.proto\x1a\x1eprivate_location/v1/otel.proto\"\x88\x05\n" + "\vHTTPMonitor\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x10\n" + "\x03url\x18\x02 \x01(\tR\x03url\x12 \n" + @@ -236,7 +189,9 @@ const file_private_location_v1_http_monitor_proto_rawDesc = "" + " \x03(\v2\x1c.private_location.v1.HeadersR\aheaders\x12^\n" + "\x16status_code_assertions\x18\v \x03(\v2(.private_location.v1.StatusCodeAssertionR\x14statusCodeAssertions\x12K\n" + "\x0fbody_assertions\x18\f \x03(\v2\".private_location.v1.BodyAssertionR\x0ebodyAssertions\x12Q\n" + - "\x11header_assertions\x18\r \x03(\v2$.private_location.v1.HeaderAssertionR\x10headerAssertionsB\x0e\n" + + "\x11header_assertions\x18\r \x03(\v2$.private_location.v1.HeaderAssertionR\x10headerAssertions\x12@\n" + + "\votel_config\x18\x14 \x01(\v2\x1f.private_location.v1.OtelConfigR\n" + + "otelConfigB\x0e\n" + "\f_degraded_atBJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" var ( @@ -251,24 +206,26 @@ func file_private_location_v1_http_monitor_proto_rawDescGZIP() []byte { return file_private_location_v1_http_monitor_proto_rawDescData } -var file_private_location_v1_http_monitor_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_private_location_v1_http_monitor_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_private_location_v1_http_monitor_proto_goTypes = []any{ - (*Headers)(nil), // 0: private_location.v1.Headers - (*HTTPMonitor)(nil), // 1: private_location.v1.HTTPMonitor + (*HTTPMonitor)(nil), // 0: private_location.v1.HTTPMonitor + (*Headers)(nil), // 1: private_location.v1.Headers (*StatusCodeAssertion)(nil), // 2: private_location.v1.StatusCodeAssertion (*BodyAssertion)(nil), // 3: private_location.v1.BodyAssertion (*HeaderAssertion)(nil), // 4: private_location.v1.HeaderAssertion + (*OtelConfig)(nil), // 5: private_location.v1.OtelConfig } var file_private_location_v1_http_monitor_proto_depIdxs = []int32{ - 0, // 0: private_location.v1.HTTPMonitor.headers:type_name -> private_location.v1.Headers + 1, // 0: private_location.v1.HTTPMonitor.headers:type_name -> private_location.v1.Headers 2, // 1: private_location.v1.HTTPMonitor.status_code_assertions:type_name -> private_location.v1.StatusCodeAssertion 3, // 2: private_location.v1.HTTPMonitor.body_assertions:type_name -> private_location.v1.BodyAssertion 4, // 3: private_location.v1.HTTPMonitor.header_assertions:type_name -> private_location.v1.HeaderAssertion - 4, // [4:4] is the sub-list for method output_type - 4, // [4:4] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 5, // 4: private_location.v1.HTTPMonitor.otel_config:type_name -> private_location.v1.OtelConfig + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name } func init() { file_private_location_v1_http_monitor_proto_init() } @@ -277,14 +234,15 @@ func file_private_location_v1_http_monitor_proto_init() { return } file_private_location_v1_assertions_proto_init() - file_private_location_v1_http_monitor_proto_msgTypes[1].OneofWrappers = []any{} + file_private_location_v1_otel_proto_init() + file_private_location_v1_http_monitor_proto_msgTypes[0].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_http_monitor_proto_rawDesc), len(file_private_location_v1_http_monitor_proto_rawDesc)), NumEnums: 0, - NumMessages: 2, + NumMessages: 1, NumExtensions: 0, NumServices: 0, }, diff --git a/apps/private-location/proto/private_location/v1/otel.pb.go b/apps/private-location/proto/private_location/v1/otel.pb.go new file mode 100644 index 00000000..4a819edb --- /dev/null +++ b/apps/private-location/proto/private_location/v1/otel.pb.go @@ -0,0 +1,189 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: private_location/v1/otel.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Headers struct { + state protoimpl.MessageState `protogen:"open.v1"` + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Headers) Reset() { + *x = Headers{} + mi := &file_private_location_v1_otel_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Headers) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Headers) ProtoMessage() {} + +func (x *Headers) ProtoReflect() protoreflect.Message { + mi := &file_private_location_v1_otel_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Headers.ProtoReflect.Descriptor instead. +func (*Headers) Descriptor() ([]byte, []int) { + return file_private_location_v1_otel_proto_rawDescGZIP(), []int{0} +} + +func (x *Headers) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *Headers) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +type OtelConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Endpoint string `protobuf:"bytes,1,opt,name=endpoint,proto3" json:"endpoint,omitempty"` + Headers []*Headers `protobuf:"bytes,2,rep,name=headers,proto3" json:"headers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OtelConfig) Reset() { + *x = OtelConfig{} + mi := &file_private_location_v1_otel_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OtelConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OtelConfig) ProtoMessage() {} + +func (x *OtelConfig) ProtoReflect() protoreflect.Message { + mi := &file_private_location_v1_otel_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OtelConfig.ProtoReflect.Descriptor instead. +func (*OtelConfig) Descriptor() ([]byte, []int) { + return file_private_location_v1_otel_proto_rawDescGZIP(), []int{1} +} + +func (x *OtelConfig) GetEndpoint() string { + if x != nil { + return x.Endpoint + } + return "" +} + +func (x *OtelConfig) GetHeaders() []*Headers { + if x != nil { + return x.Headers + } + return nil +} + +var File_private_location_v1_otel_proto protoreflect.FileDescriptor + +const file_private_location_v1_otel_proto_rawDesc = "" + + "\n" + + "\x1eprivate_location/v1/otel.proto\x12\x13private_location.v1\"1\n" + + "\aHeaders\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value\"`\n" + + "\n" + + "OtelConfig\x12\x1a\n" + + "\bendpoint\x18\x01 \x01(\tR\bendpoint\x126\n" + + "\aheaders\x18\x02 \x03(\v2\x1c.private_location.v1.HeadersR\aheadersBJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" + +var ( + file_private_location_v1_otel_proto_rawDescOnce sync.Once + file_private_location_v1_otel_proto_rawDescData []byte +) + +func file_private_location_v1_otel_proto_rawDescGZIP() []byte { + file_private_location_v1_otel_proto_rawDescOnce.Do(func() { + file_private_location_v1_otel_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_private_location_v1_otel_proto_rawDesc), len(file_private_location_v1_otel_proto_rawDesc))) + }) + return file_private_location_v1_otel_proto_rawDescData +} + +var file_private_location_v1_otel_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_private_location_v1_otel_proto_goTypes = []any{ + (*Headers)(nil), // 0: private_location.v1.Headers + (*OtelConfig)(nil), // 1: private_location.v1.OtelConfig +} +var file_private_location_v1_otel_proto_depIdxs = []int32{ + 0, // 0: private_location.v1.OtelConfig.headers:type_name -> private_location.v1.Headers + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_private_location_v1_otel_proto_init() } +func file_private_location_v1_otel_proto_init() { + if File_private_location_v1_otel_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_otel_proto_rawDesc), len(file_private_location_v1_otel_proto_rawDesc)), + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_private_location_v1_otel_proto_goTypes, + DependencyIndexes: file_private_location_v1_otel_proto_depIdxs, + MessageInfos: file_private_location_v1_otel_proto_msgTypes, + }.Build() + File_private_location_v1_otel_proto = out.File + file_private_location_v1_otel_proto_goTypes = nil + file_private_location_v1_otel_proto_depIdxs = nil +} diff --git a/apps/private-location/proto/private_location/v1/private_location.pb.go b/apps/private-location/proto/private_location/v1/private_location.pb.go index 71a8399a..e31eb986 100644 --- a/apps/private-location/proto/private_location/v1/private_location.pb.go +++ b/apps/private-location/proto/private_location/v1/private_location.pb.go @@ -9,7 +9,6 @@ package v1 import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" - _ "google.golang.org/protobuf/types/known/structpb" reflect "reflect" sync "sync" unsafe "unsafe" @@ -63,6 +62,7 @@ type MonitorsResponse struct { HttpMonitors []*HTTPMonitor `protobuf:"bytes,1,rep,name=http_monitors,json=httpMonitors,proto3" json:"http_monitors,omitempty"` TcpMonitors []*TCPMonitor `protobuf:"bytes,2,rep,name=tcp_monitors,json=tcpMonitors,proto3" json:"tcp_monitors,omitempty"` DnsMonitors []*DNSMonitor `protobuf:"bytes,3,rep,name=dns_monitors,json=dnsMonitors,proto3" json:"dns_monitors,omitempty"` + Region string `protobuf:"bytes,4,opt,name=region,proto3" json:"region,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -118,6 +118,13 @@ func (x *MonitorsResponse) GetDnsMonitors() []*DNSMonitor { return nil } +func (x *MonitorsResponse) GetRegion() string { + if x != nil { + return x.Region + } + return "" +} + type IngestTCPRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` @@ -654,12 +661,13 @@ var File_private_location_v1_private_location_proto protoreflect.FileDescriptor const file_private_location_v1_private_location_proto_rawDesc = "" + "\n" + - "*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" + - "\x0fMonitorsRequest\"\xe1\x01\n" + + "*private_location/v1/private_location.proto\x12\x13private_location.v1\x1a%private_location/v1/dns_monitor.proto\x1a&private_location/v1/http_monitor.proto\x1a%private_location/v1/tcp_monitor.proto\"\x11\n" + + "\x0fMonitorsRequest\"\xf9\x01\n" + "\x10MonitorsResponse\x12E\n" + "\rhttp_monitors\x18\x01 \x03(\v2 .private_location.v1.HTTPMonitorR\fhttpMonitors\x12B\n" + "\ftcp_monitors\x18\x02 \x03(\v2\x1f.private_location.v1.TCPMonitorR\vtcpMonitors\x12B\n" + - "\fdns_monitors\x18\x03 \x03(\v2\x1f.private_location.v1.DNSMonitorR\vdnsMonitors\"\x9e\x02\n" + + "\fdns_monitors\x18\x03 \x03(\v2\x1f.private_location.v1.DNSMonitorR\vdnsMonitors\x12\x16\n" + + "\x06region\x18\x04 \x01(\tR\x06region\"\x9e\x02\n" + "\x10IngestTCPRequest\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + diff --git a/apps/private-location/proto/private_location/v1/tcp_monitor.pb.go b/apps/private-location/proto/private_location/v1/tcp_monitor.pb.go index 3cc2ba3a..e67daf94 100644 --- a/apps/private-location/proto/private_location/v1/tcp_monitor.pb.go +++ b/apps/private-location/proto/private_location/v1/tcp_monitor.pb.go @@ -29,6 +29,7 @@ type TCPMonitor struct { DegradedAt *int64 `protobuf:"varint,4,opt,name=degraded_at,json=degradedAt,proto3,oneof" json:"degraded_at,omitempty"` Periodicity string `protobuf:"bytes,5,opt,name=periodicity,proto3" json:"periodicity,omitempty"` Retry int64 `protobuf:"varint,6,opt,name=retry,proto3" json:"retry,omitempty"` + OtelConfig *OtelConfig `protobuf:"bytes,20,opt,name=otel_config,json=otelConfig,proto3" json:"otel_config,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -105,11 +106,18 @@ func (x *TCPMonitor) GetRetry() int64 { return 0 } +func (x *TCPMonitor) GetOtelConfig() *OtelConfig { + if x != nil { + return x.OtelConfig + } + return nil +} + var File_private_location_v1_tcp_monitor_proto protoreflect.FileDescriptor const file_private_location_v1_tcp_monitor_proto_rawDesc = "" + "\n" + - "%private_location/v1/tcp_monitor.proto\x12\x13private_location.v1\"\xb6\x01\n" + + "%private_location/v1/tcp_monitor.proto\x12\x13private_location.v1\x1a\x1eprivate_location/v1/otel.proto\"\xf8\x01\n" + "\n" + "TCPMonitor\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x10\n" + @@ -118,7 +126,9 @@ const file_private_location_v1_tcp_monitor_proto_rawDesc = "" + "\vdegraded_at\x18\x04 \x01(\x03H\x00R\n" + "degradedAt\x88\x01\x01\x12 \n" + "\vperiodicity\x18\x05 \x01(\tR\vperiodicity\x12\x14\n" + - "\x05retry\x18\x06 \x01(\x03R\x05retryB\x0e\n" + + "\x05retry\x18\x06 \x01(\x03R\x05retry\x12@\n" + + "\votel_config\x18\x14 \x01(\v2\x1f.private_location.v1.OtelConfigR\n" + + "otelConfigB\x0e\n" + "\f_degraded_atBJZHgithub.com/openstatushq/openstatus/packages/proto/private_location/v1;v1b\x06proto3" var ( @@ -136,13 +146,15 @@ func file_private_location_v1_tcp_monitor_proto_rawDescGZIP() []byte { var file_private_location_v1_tcp_monitor_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_private_location_v1_tcp_monitor_proto_goTypes = []any{ (*TCPMonitor)(nil), // 0: private_location.v1.TCPMonitor + (*OtelConfig)(nil), // 1: private_location.v1.OtelConfig } var file_private_location_v1_tcp_monitor_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 1, // 0: private_location.v1.TCPMonitor.otel_config:type_name -> private_location.v1.OtelConfig + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name } func init() { file_private_location_v1_tcp_monitor_proto_init() } @@ -150,6 +162,7 @@ func file_private_location_v1_tcp_monitor_proto_init() { if File_private_location_v1_tcp_monitor_proto != nil { return } + file_private_location_v1_otel_proto_init() file_private_location_v1_tcp_monitor_proto_msgTypes[0].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ diff --git a/packages/proto/internal/private_location/v1/http_monitor.proto b/packages/proto/internal/private_location/v1/http_monitor.proto index 444ddb8a..2cf579cd 100644 --- a/packages/proto/internal/private_location/v1/http_monitor.proto +++ b/packages/proto/internal/private_location/v1/http_monitor.proto @@ -3,15 +3,11 @@ syntax = "proto3"; package private_location.v1; import "private_location/v1/assertions.proto"; +import "private_location/v1/otel.proto"; option go_package = "github.com/openstatushq/openstatus/packages/proto/private_location/v1;v1"; -message Headers { - string key = 1; - string value = 2; -} - message HTTPMonitor { string id = 1; string url = 2; @@ -29,4 +25,6 @@ message HTTPMonitor { repeated BodyAssertion body_assertions = 12; repeated HeaderAssertion header_assertions = 13; + OtelConfig otel_config = 20; + } diff --git a/packages/proto/internal/private_location/v1/otel.proto b/packages/proto/internal/private_location/v1/otel.proto new file mode 100644 index 00000000..bef56b25 --- /dev/null +++ b/packages/proto/internal/private_location/v1/otel.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package private_location.v1; + +option go_package = "github.com/openstatushq/openstatus/packages/proto/private_location/v1;v1"; + +message Headers { + string key = 1; + string value = 2; +} + +message OtelConfig { + string endpoint = 1; + repeated Headers headers = 2; +} diff --git a/packages/proto/internal/private_location/v1/private_location.proto b/packages/proto/internal/private_location/v1/private_location.proto index 6c893ccc..28ce249c 100644 --- a/packages/proto/internal/private_location/v1/private_location.proto +++ b/packages/proto/internal/private_location/v1/private_location.proto @@ -23,6 +23,7 @@ message MonitorsResponse { repeated HTTPMonitor http_monitors = 1; repeated TCPMonitor tcp_monitors = 2; repeated DNSMonitor dns_monitors = 3; + string region = 4; } diff --git a/packages/proto/internal/private_location/v1/tcp_monitor.proto b/packages/proto/internal/private_location/v1/tcp_monitor.proto index b14cfef9..d2a58008 100644 --- a/packages/proto/internal/private_location/v1/tcp_monitor.proto +++ b/packages/proto/internal/private_location/v1/tcp_monitor.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package private_location.v1; +import "private_location/v1/otel.proto"; + option go_package = "github.com/openstatushq/openstatus/packages/proto/private_location/v1;v1"; @@ -15,4 +17,6 @@ message TCPMonitor { string periodicity = 5; int64 retry = 6; + OtelConfig otel_config = 20; + } -- 2.51.2