feat: implement service ping (#10080)

This PR is still WIP and needs changes to at least the tests.

# Which Problems Are Solved

To be able to report analytical / telemetry data from deployed Zitadel
systems back to a central endpoint, we designed a "service ping"
functionality. See also https://github.com/zitadel/zitadel/issues/9706.
This PR adds the first implementation to allow collection base data as
well as report amount of resources such as organizations, users per
organization and more.

# How the Problems Are Solved

- Added a worker to handle the different `ReportType` variations. 
- Schedule a periodic job to start a `ServicePingReport`
- Configuration added to allow customization of what data will be
reported
- Setup step to generate and store a `systemID`

# Additional Changes

None

# Additional Context

relates to #9869
This commit is contained in:
Livio Spring
2025-07-02 07:57:41 -04:00
committed by GitHub
parent 71575e8d67
commit f93a35c7a8
18 changed files with 1854 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
package mock
//go:generate mockgen -package mock -destination queue.mock.go github.com/zitadel/zitadel/internal/serviceping Queue
//go:generate mockgen -package mock -destination queries.mock.go github.com/zitadel/zitadel/internal/serviceping Queries
//go:generate mockgen -package mock -destination telemetry.mock.go github.com/zitadel/zitadel/pkg/grpc/analytics/v2beta TelemetryServiceClient

View File

@@ -0,0 +1,72 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/zitadel/zitadel/internal/serviceping (interfaces: Queries)
//
// Generated by this command:
//
// mockgen -package mock -destination queries.mock.go github.com/zitadel/zitadel/internal/serviceping Queries
//
// Package mock is a generated GoMock package.
package mock
import (
context "context"
reflect "reflect"
query "github.com/zitadel/zitadel/internal/query"
gomock "go.uber.org/mock/gomock"
)
// MockQueries is a mock of Queries interface.
type MockQueries struct {
ctrl *gomock.Controller
recorder *MockQueriesMockRecorder
isgomock struct{}
}
// MockQueriesMockRecorder is the mock recorder for MockQueries.
type MockQueriesMockRecorder struct {
mock *MockQueries
}
// NewMockQueries creates a new mock instance.
func NewMockQueries(ctrl *gomock.Controller) *MockQueries {
mock := &MockQueries{ctrl: ctrl}
mock.recorder = &MockQueriesMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockQueries) EXPECT() *MockQueriesMockRecorder {
return m.recorder
}
// ListResourceCounts mocks base method.
func (m *MockQueries) ListResourceCounts(ctx context.Context, lastID, size int) ([]query.ResourceCount, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListResourceCounts", ctx, lastID, size)
ret0, _ := ret[0].([]query.ResourceCount)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListResourceCounts indicates an expected call of ListResourceCounts.
func (mr *MockQueriesMockRecorder) ListResourceCounts(ctx, lastID, size any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListResourceCounts", reflect.TypeOf((*MockQueries)(nil).ListResourceCounts), ctx, lastID, size)
}
// SearchInstances mocks base method.
func (m *MockQueries) SearchInstances(ctx context.Context, queries *query.InstanceSearchQueries) (*query.Instances, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SearchInstances", ctx, queries)
ret0, _ := ret[0].(*query.Instances)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SearchInstances indicates an expected call of SearchInstances.
func (mr *MockQueriesMockRecorder) SearchInstances(ctx, queries any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchInstances", reflect.TypeOf((*MockQueries)(nil).SearchInstances), ctx, queries)
}

View File

@@ -0,0 +1,62 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/zitadel/zitadel/internal/serviceping (interfaces: Queue)
//
// Generated by this command:
//
// mockgen -package mock -destination queue.mock.go github.com/zitadel/zitadel/internal/serviceping Queue
//
// Package mock is a generated GoMock package.
package mock
import (
context "context"
reflect "reflect"
river "github.com/riverqueue/river"
queue "github.com/zitadel/zitadel/internal/queue"
gomock "go.uber.org/mock/gomock"
)
// MockQueue is a mock of Queue interface.
type MockQueue struct {
ctrl *gomock.Controller
recorder *MockQueueMockRecorder
isgomock struct{}
}
// MockQueueMockRecorder is the mock recorder for MockQueue.
type MockQueueMockRecorder struct {
mock *MockQueue
}
// NewMockQueue creates a new mock instance.
func NewMockQueue(ctrl *gomock.Controller) *MockQueue {
mock := &MockQueue{ctrl: ctrl}
mock.recorder = &MockQueueMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockQueue) EXPECT() *MockQueueMockRecorder {
return m.recorder
}
// Insert mocks base method.
func (m *MockQueue) Insert(ctx context.Context, args river.JobArgs, opts ...queue.InsertOpt) error {
m.ctrl.T.Helper()
varargs := []any{ctx, args}
for _, a := range opts {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Insert", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// Insert indicates an expected call of Insert.
func (mr *MockQueueMockRecorder) Insert(ctx, args any, opts ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{ctx, args}, opts...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockQueue)(nil).Insert), varargs...)
}

View File

@@ -0,0 +1,83 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/zitadel/zitadel/pkg/grpc/analytics/v2beta (interfaces: TelemetryServiceClient)
//
// Generated by this command:
//
// mockgen -package mock -destination telemetry.mock.go github.com/zitadel/zitadel/pkg/grpc/analytics/v2beta TelemetryServiceClient
//
// Package mock is a generated GoMock package.
package mock
import (
context "context"
reflect "reflect"
analytics "github.com/zitadel/zitadel/pkg/grpc/analytics/v2beta"
gomock "go.uber.org/mock/gomock"
grpc "google.golang.org/grpc"
)
// MockTelemetryServiceClient is a mock of TelemetryServiceClient interface.
type MockTelemetryServiceClient struct {
ctrl *gomock.Controller
recorder *MockTelemetryServiceClientMockRecorder
isgomock struct{}
}
// MockTelemetryServiceClientMockRecorder is the mock recorder for MockTelemetryServiceClient.
type MockTelemetryServiceClientMockRecorder struct {
mock *MockTelemetryServiceClient
}
// NewMockTelemetryServiceClient creates a new mock instance.
func NewMockTelemetryServiceClient(ctrl *gomock.Controller) *MockTelemetryServiceClient {
mock := &MockTelemetryServiceClient{ctrl: ctrl}
mock.recorder = &MockTelemetryServiceClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockTelemetryServiceClient) EXPECT() *MockTelemetryServiceClientMockRecorder {
return m.recorder
}
// ReportBaseInformation mocks base method.
func (m *MockTelemetryServiceClient) ReportBaseInformation(ctx context.Context, in *analytics.ReportBaseInformationRequest, opts ...grpc.CallOption) (*analytics.ReportBaseInformationResponse, error) {
m.ctrl.T.Helper()
varargs := []any{ctx, in}
for _, a := range opts {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ReportBaseInformation", varargs...)
ret0, _ := ret[0].(*analytics.ReportBaseInformationResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ReportBaseInformation indicates an expected call of ReportBaseInformation.
func (mr *MockTelemetryServiceClientMockRecorder) ReportBaseInformation(ctx, in any, opts ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{ctx, in}, opts...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportBaseInformation", reflect.TypeOf((*MockTelemetryServiceClient)(nil).ReportBaseInformation), varargs...)
}
// ReportResourceCounts mocks base method.
func (m *MockTelemetryServiceClient) ReportResourceCounts(ctx context.Context, in *analytics.ReportResourceCountsRequest, opts ...grpc.CallOption) (*analytics.ReportResourceCountsResponse, error) {
m.ctrl.T.Helper()
varargs := []any{ctx, in}
for _, a := range opts {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ReportResourceCounts", varargs...)
ret0, _ := ret[0].(*analytics.ReportResourceCountsResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ReportResourceCounts indicates an expected call of ReportResourceCounts.
func (mr *MockTelemetryServiceClientMockRecorder) ReportResourceCounts(ctx, in any, opts ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{ctx, in}, opts...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportResourceCounts", reflect.TypeOf((*MockTelemetryServiceClient)(nil).ReportResourceCounts), varargs...)
}