zitadel/cmd/setup/sync_role_permissions.go
Tim Möhlmann e670b9126c
fix(permissions): chunked synchronization of role permission events (#9403)
# Which Problems Are Solved

Setup fails to push all role permission events when running Zitadel with
CockroachDB. `TransactionRetryError`s were visible in logs which finally
times out the setup job with `timeout: context deadline exceeded`

# How the Problems Are Solved

As suggested in the [Cockroach documentation](timeout: context deadline
exceeded), _"break down larger transactions"_. The commands to be pushed
for the role permissions are chunked in 50 events per push. This
chunking is only done with CockroachDB.

# Additional Changes

- gci run fixed some unrelated imports
- access to `command.Commands` for the setup job, so we can reuse the
sync logic.

# Additional Context

Closes #9293

---------

Co-authored-by: Silvan <27845747+adlerhurst@users.noreply.github.com>
2025-02-26 16:06:50 +00:00

77 lines
2.5 KiB
Go

package setup
import (
"context"
_ "embed"
"fmt"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/instance"
)
// SyncRolePermissions is a repeatable step which synchronizes the InternalAuthZ
// RolePermissionMappings from the configuration to the database.
// This is needed until role permissions are manageable over the API.
type SyncRolePermissions struct {
commands *command.Commands
eventstore *eventstore.Eventstore
rolePermissionMappings []authz.RoleMapping
}
func (mig *SyncRolePermissions) Execute(ctx context.Context, _ eventstore.Event) error {
if err := mig.executeSystem(ctx); err != nil {
return err
}
return mig.executeInstances(ctx)
}
func (mig *SyncRolePermissions) executeSystem(ctx context.Context) error {
logging.WithFields("migration", mig.String()).Info("prepare system role permission sync events")
details, err := mig.commands.SynchronizeRolePermission(ctx, "SYSTEM", mig.rolePermissionMappings)
if err != nil {
return err
}
logging.WithFields("migration", mig.String(), "sequence", details.Sequence).Info("pushed system role permission sync events")
return nil
}
func (mig *SyncRolePermissions) executeInstances(ctx context.Context) error {
instances, err := mig.eventstore.InstanceIDs(
ctx,
eventstore.NewSearchQueryBuilder(eventstore.ColumnsInstanceIDs).
OrderDesc().
AddQuery().
AggregateTypes(instance.AggregateType).
EventTypes(instance.InstanceAddedEventType).
Builder().
ExcludeAggregateIDs().
AggregateTypes(instance.AggregateType).
EventTypes(instance.InstanceRemovedEventType).
Builder(),
)
if err != nil {
return err
}
for i, instanceID := range instances {
logging.WithFields("instance_id", instanceID, "migration", mig.String(), "progress", fmt.Sprintf("%d/%d", i+1, len(instances))).Info("prepare instance role permission sync events")
details, err := mig.commands.SynchronizeRolePermission(ctx, instanceID, mig.rolePermissionMappings)
if err != nil {
return err
}
logging.WithFields("instance_id", instanceID, "migration", mig.String(), "sequence", details.Sequence).Info("pushed instance role permission sync events")
}
return nil
}
func (*SyncRolePermissions) String() string {
return "repeatable_sync_role_permissions"
}
func (*SyncRolePermissions) Check(lastRun map[string]interface{}) bool {
return true
}