feat: exchange gRPC server implementation to connectRPC (#10145)

# Which Problems Are Solved

The current maintained gRPC server in combination with a REST (grpc)
gateway is getting harder and harder to maintain. Additionally, there
have been and still are issues with supporting / displaying `oneOf`s
correctly.
We therefore decided to exchange the server implementation to
connectRPC, which apart from supporting connect as protocol, also also
"standard" gRCP clients as well as HTTP/1.1 / rest like clients, e.g.
curl directly call the server without any additional gateway.

# How the Problems Are Solved

- All v2 services are moved to connectRPC implementation. (v1 services
are still served as pure grpc servers)
- All gRPC server interceptors were migrated / copied to a corresponding
connectRPC interceptor.
- API.ListGrpcServices and API. ListGrpcMethods were changed to include
the connect services and endpoints.
- gRPC server reflection was changed to a `StaticReflector` using the
`ListGrpcServices` list.
- The `grpc.Server` interfaces was split into different combinations to
be able to handle the different cases (grpc server and prefixed gateway,
connect server with grpc gateway, connect server only, ...)
- Docs of services serving connectRPC only with no additional gateway
(instance, webkey, project, app, org v2 beta) are changed to expose that
- since the plugin is not yet available on buf, we download it using
`postinstall` hook of the docs

# Additional Changes

- WebKey service is added as v2 service (in addition to the current
v2beta)

# Additional Context

closes #9483

---------

Co-authored-by: Elio Bischof <elio@zitadel.com>
This commit is contained in:
Livio Spring
2025-07-04 10:06:20 -04:00
committed by GitHub
parent 82cd1cee08
commit 9ebf2316c6
133 changed files with 5191 additions and 1187 deletions

View File

@@ -3,6 +3,7 @@ package action
import (
"context"
"connectrpc.com/connect"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/api/authz"
@@ -13,8 +14,8 @@ import (
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
)
func (s *Server) SetExecution(ctx context.Context, req *action.SetExecutionRequest) (*action.SetExecutionResponse, error) {
reqTargets := req.GetTargets()
func (s *Server) SetExecution(ctx context.Context, req *connect.Request[action.SetExecutionRequest]) (*connect.Response[action.SetExecutionResponse], error) {
reqTargets := req.Msg.GetTargets()
targets := make([]*execution.Target, len(reqTargets))
for i, target := range reqTargets {
targets[i] = &execution.Target{Type: domain.ExecutionTargetTypeTarget, Target: target}
@@ -25,7 +26,7 @@ func (s *Server) SetExecution(ctx context.Context, req *action.SetExecutionReque
var err error
var details *domain.ObjectDetails
instanceID := authz.GetInstance(ctx).InstanceID()
switch t := req.GetCondition().GetConditionType().(type) {
switch t := req.Msg.GetCondition().GetConditionType().(type) {
case *action.Condition_Request:
cond := executionConditionFromRequest(t.Request)
details, err = s.command.SetExecutionRequest(ctx, cond, set, instanceID)
@@ -43,27 +44,27 @@ func (s *Server) SetExecution(ctx context.Context, req *action.SetExecutionReque
if err != nil {
return nil, err
}
return &action.SetExecutionResponse{
return connect.NewResponse(&action.SetExecutionResponse{
SetDate: timestamppb.New(details.EventDate),
}, nil
}), nil
}
func (s *Server) ListExecutionFunctions(ctx context.Context, _ *action.ListExecutionFunctionsRequest) (*action.ListExecutionFunctionsResponse, error) {
return &action.ListExecutionFunctionsResponse{
func (s *Server) ListExecutionFunctions(ctx context.Context, _ *connect.Request[action.ListExecutionFunctionsRequest]) (*connect.Response[action.ListExecutionFunctionsResponse], error) {
return connect.NewResponse(&action.ListExecutionFunctionsResponse{
Functions: s.ListActionFunctions(),
}, nil
}), nil
}
func (s *Server) ListExecutionMethods(ctx context.Context, _ *action.ListExecutionMethodsRequest) (*action.ListExecutionMethodsResponse, error) {
return &action.ListExecutionMethodsResponse{
func (s *Server) ListExecutionMethods(ctx context.Context, _ *connect.Request[action.ListExecutionMethodsRequest]) (*connect.Response[action.ListExecutionMethodsResponse], error) {
return connect.NewResponse(&action.ListExecutionMethodsResponse{
Methods: s.ListGRPCMethods(),
}, nil
}), nil
}
func (s *Server) ListExecutionServices(ctx context.Context, _ *action.ListExecutionServicesRequest) (*action.ListExecutionServicesResponse, error) {
return &action.ListExecutionServicesResponse{
func (s *Server) ListExecutionServices(ctx context.Context, _ *connect.Request[action.ListExecutionServicesRequest]) (*connect.Response[action.ListExecutionServicesResponse], error) {
return connect.NewResponse(&action.ListExecutionServicesResponse{
Services: s.ListGRPCServices(),
}, nil
}), nil
}
func executionConditionFromRequest(request *action.RequestExecution) *command.ExecutionAPICondition {

View File

@@ -4,6 +4,7 @@ import (
"context"
"strings"
"connectrpc.com/connect"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
@@ -22,14 +23,14 @@ const (
conditionIDEventGroupSegmentCount = 1
)
func (s *Server) GetTarget(ctx context.Context, req *action.GetTargetRequest) (*action.GetTargetResponse, error) {
resp, err := s.query.GetTargetByID(ctx, req.GetId())
func (s *Server) GetTarget(ctx context.Context, req *connect.Request[action.GetTargetRequest]) (*connect.Response[action.GetTargetResponse], error) {
resp, err := s.query.GetTargetByID(ctx, req.Msg.GetId())
if err != nil {
return nil, err
}
return &action.GetTargetResponse{
return connect.NewResponse(&action.GetTargetResponse{
Target: targetToPb(resp),
}, nil
}), nil
}
type InstanceContext interface {
@@ -41,8 +42,8 @@ type Context interface {
GetOwner() InstanceContext
}
func (s *Server) ListTargets(ctx context.Context, req *action.ListTargetsRequest) (*action.ListTargetsResponse, error) {
queries, err := s.ListTargetsRequestToModel(req)
func (s *Server) ListTargets(ctx context.Context, req *connect.Request[action.ListTargetsRequest]) (*connect.Response[action.ListTargetsResponse], error) {
queries, err := s.ListTargetsRequestToModel(req.Msg)
if err != nil {
return nil, err
}
@@ -50,14 +51,14 @@ func (s *Server) ListTargets(ctx context.Context, req *action.ListTargetsRequest
if err != nil {
return nil, err
}
return &action.ListTargetsResponse{
return connect.NewResponse(&action.ListTargetsResponse{
Result: targetsToPb(resp.Targets),
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, resp.SearchResponse),
}, nil
}), nil
}
func (s *Server) ListExecutions(ctx context.Context, req *action.ListExecutionsRequest) (*action.ListExecutionsResponse, error) {
queries, err := s.ListExecutionsRequestToModel(req)
func (s *Server) ListExecutions(ctx context.Context, req *connect.Request[action.ListExecutionsRequest]) (*connect.Response[action.ListExecutionsResponse], error) {
queries, err := s.ListExecutionsRequestToModel(req.Msg)
if err != nil {
return nil, err
}
@@ -65,10 +66,10 @@ func (s *Server) ListExecutions(ctx context.Context, req *action.ListExecutionsR
if err != nil {
return nil, err
}
return &action.ListExecutionsResponse{
return connect.NewResponse(&action.ListExecutionsResponse{
Result: executionsToPb(resp.Executions),
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, resp.SearchResponse),
}, nil
}), nil
}
func targetsToPb(targets []*query.Target) []*action.Target {

View File

@@ -1,7 +1,10 @@
package action
import (
"google.golang.org/grpc"
"net/http"
"connectrpc.com/connect"
"google.golang.org/protobuf/reflect/protoreflect"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/server"
@@ -9,12 +12,12 @@ import (
"github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/query"
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
"github.com/zitadel/zitadel/pkg/grpc/action/v2beta/actionconnect"
)
var _ action.ActionServiceServer = (*Server)(nil)
var _ actionconnect.ActionServiceHandler = (*Server)(nil)
type Server struct {
action.UnimplementedActionServiceServer
systemDefaults systemdefaults.SystemDefaults
command *command.Commands
query *query.Queries
@@ -43,8 +46,12 @@ func CreateServer(
}
}
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
action.RegisterActionServiceServer(grpcServer, s)
func (s *Server) RegisterConnectServer(interceptors ...connect.Interceptor) (string, http.Handler) {
return actionconnect.NewActionServiceHandler(s, connect.WithInterceptors(interceptors...))
}
func (s *Server) FileDescriptor() protoreflect.FileDescriptor {
return action.File_zitadel_action_v2beta_action_service_proto
}
func (s *Server) AppName() string {

View File

@@ -3,6 +3,7 @@ package action
import (
"context"
"connectrpc.com/connect"
"github.com/muhlemmer/gu"
"google.golang.org/protobuf/types/known/timestamppb"
@@ -13,8 +14,8 @@ import (
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
)
func (s *Server) CreateTarget(ctx context.Context, req *action.CreateTargetRequest) (*action.CreateTargetResponse, error) {
add := createTargetToCommand(req)
func (s *Server) CreateTarget(ctx context.Context, req *connect.Request[action.CreateTargetRequest]) (*connect.Response[action.CreateTargetResponse], error) {
add := createTargetToCommand(req.Msg)
instanceID := authz.GetInstance(ctx).InstanceID()
createdAt, err := s.command.AddTarget(ctx, add, instanceID)
if err != nil {
@@ -24,16 +25,16 @@ func (s *Server) CreateTarget(ctx context.Context, req *action.CreateTargetReque
if !createdAt.IsZero() {
creationDate = timestamppb.New(createdAt)
}
return &action.CreateTargetResponse{
return connect.NewResponse(&action.CreateTargetResponse{
Id: add.AggregateID,
CreationDate: creationDate,
SigningKey: add.SigningKey,
}, nil
}), nil
}
func (s *Server) UpdateTarget(ctx context.Context, req *action.UpdateTargetRequest) (*action.UpdateTargetResponse, error) {
func (s *Server) UpdateTarget(ctx context.Context, req *connect.Request[action.UpdateTargetRequest]) (*connect.Response[action.UpdateTargetResponse], error) {
instanceID := authz.GetInstance(ctx).InstanceID()
update := updateTargetToCommand(req)
update := updateTargetToCommand(req.Msg)
changedAt, err := s.command.ChangeTarget(ctx, update, instanceID)
if err != nil {
return nil, err
@@ -42,15 +43,15 @@ func (s *Server) UpdateTarget(ctx context.Context, req *action.UpdateTargetReque
if !changedAt.IsZero() {
changeDate = timestamppb.New(changedAt)
}
return &action.UpdateTargetResponse{
return connect.NewResponse(&action.UpdateTargetResponse{
ChangeDate: changeDate,
SigningKey: update.SigningKey,
}, nil
}), nil
}
func (s *Server) DeleteTarget(ctx context.Context, req *action.DeleteTargetRequest) (*action.DeleteTargetResponse, error) {
func (s *Server) DeleteTarget(ctx context.Context, req *connect.Request[action.DeleteTargetRequest]) (*connect.Response[action.DeleteTargetResponse], error) {
instanceID := authz.GetInstance(ctx).InstanceID()
deletedAt, err := s.command.DeleteTarget(ctx, req.GetId(), instanceID)
deletedAt, err := s.command.DeleteTarget(ctx, req.Msg.GetId(), instanceID)
if err != nil {
return nil, err
}
@@ -58,9 +59,9 @@ func (s *Server) DeleteTarget(ctx context.Context, req *action.DeleteTargetReque
if !deletedAt.IsZero() {
deletionDate = timestamppb.New(deletedAt)
}
return &action.DeleteTargetResponse{
return connect.NewResponse(&action.DeleteTargetResponse{
DeletionDate: deletionDate,
}, nil
}), nil
}
func createTargetToCommand(req *action.CreateTargetRequest) *command.AddTarget {