feat: Feature flag for relational tables (#10599)

# Which Problems Are Solved

This PR introduces a new feature flag `EnableRelationalTables` that will
be used in following implementations to decide whether Zitadel should
use the relational model or the event sourcing one.

# TODO

  - [x] Implement flag at system level
- [x] Display the flag on console:
https://github.com/zitadel/zitadel/pull/10615

# How the Problems Are Solved

  - Implement loading the flag from config
- Add persistence of the flag through gRPC endpoint
(SetInstanceFeatures)
- Implement reading of the flag through gRPC endpoint
(GetInstanceFeatures)

# Additional Changes

Some minor refactoring to remove un-needed generics annotations

# Additional Context

- Closes #10574

---------

Co-authored-by: Silvan <27845747+adlerhurst@users.noreply.github.com>
This commit is contained in:
Marco A.
2025-09-02 11:48:46 +02:00
committed by GitHub
parent e3dff2482e
commit 75a67be669
46 changed files with 669 additions and 91 deletions

View File

@@ -3,12 +3,15 @@ package command
import (
"context"
"encoding/json"
"net/url"
"slices"
"strings"
"testing"
"time"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"golang.org/x/text/language"
@@ -18,6 +21,7 @@ import (
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/feature"
"github.com/zitadel/zitadel/internal/id"
id_mock "github.com/zitadel/zitadel/internal/id/mock"
"github.com/zitadel/zitadel/internal/repository/instance"
@@ -1727,3 +1731,126 @@ func TestCommandSide_RemoveInstance(t *testing.T) {
})
}
}
func TestInstanceSetupFeatures_ToInstanceFeatures(t *testing.T) {
t.Parallel()
type fields struct {
LoginDefaultOrg *bool
UserSchema *bool
TokenExchange *bool
ImprovedPerformance []feature.ImprovedPerformanceType
DebugOIDCParentError *bool
OIDCSingleV1SessionTermination *bool
EnableBackChannelLogout *bool
LoginV2 *InstanceSetupFeatureLoginV2
PermissionCheckV2 *bool
ConsoleUseV2UserApi *bool
EnableRelationalTables *bool
}
correctlyParsedURI, err := url.Parse("https://example.com")
require.NoError(t, err)
tt := []struct {
name string
fields fields
want *InstanceFeatures
wantErr bool
}{
{
name: "nil features returns nil",
fields: fields{},
want: &InstanceFeatures{},
},
{
name: "all fields no login v2",
fields: fields{
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(false),
TokenExchange: gu.Ptr(true),
ImprovedPerformance: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgDomainVerified},
DebugOIDCParentError: gu.Ptr(true),
OIDCSingleV1SessionTermination: gu.Ptr(false),
EnableBackChannelLogout: gu.Ptr(true),
PermissionCheckV2: gu.Ptr(true),
ConsoleUseV2UserApi: gu.Ptr(false),
EnableRelationalTables: gu.Ptr(true),
},
want: &InstanceFeatures{
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(false),
TokenExchange: gu.Ptr(true),
ImprovedPerformance: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgDomainVerified},
DebugOIDCParentError: gu.Ptr(true),
OIDCSingleV1SessionTermination: gu.Ptr(false),
EnableBackChannelLogout: gu.Ptr(true),
LoginV2: nil,
PermissionCheckV2: gu.Ptr(true),
ConsoleUseV2UserApi: gu.Ptr(false),
EnableRelationalTables: gu.Ptr(true),
},
},
{
name: "with login v2 no base uri",
fields: fields{
LoginV2: &InstanceSetupFeatureLoginV2{
Required: true,
},
},
want: &InstanceFeatures{
LoginV2: &feature.LoginV2{
Required: true,
},
},
},
{
name: "with login v2 valid base uri",
fields: fields{
LoginV2: &InstanceSetupFeatureLoginV2{
Required: true,
BaseURI: gu.Ptr("https://example.com"),
},
},
want: &InstanceFeatures{
LoginV2: &feature.LoginV2{
Required: true,
BaseURI: correctlyParsedURI,
},
},
},
{
name: "with login v2 invalid base uri",
fields: fields{
LoginV2: &InstanceSetupFeatureLoginV2{
Required: true,
BaseURI: gu.Ptr("://invalid"),
},
},
wantErr: true,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
f := &InstanceSetupFeatures{
LoginDefaultOrg: tc.fields.LoginDefaultOrg,
UserSchema: tc.fields.UserSchema,
TokenExchange: tc.fields.TokenExchange,
ImprovedPerformance: tc.fields.ImprovedPerformance,
DebugOIDCParentError: tc.fields.DebugOIDCParentError,
OIDCSingleV1SessionTermination: tc.fields.OIDCSingleV1SessionTermination,
EnableBackChannelLogout: tc.fields.EnableBackChannelLogout,
LoginV2: tc.fields.LoginV2,
PermissionCheckV2: tc.fields.PermissionCheckV2,
ConsoleUseV2UserApi: tc.fields.ConsoleUseV2UserApi,
EnableRelationalTables: tc.fields.EnableRelationalTables,
}
got, err := f.ToInstanceFeatures()
require.Equal(t, tc.wantErr, err != nil)
assert.Equal(t, tc.want, got)
})
}
}