From fbd25974af1a95c8bec19b83280ea99801104aec Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Wed, 11 Dec 2024 14:50:10 +0100 Subject: [PATCH] feat(seeding): add parallalization to seeding Refs: https://github.com/eclipse-tractusx/portal-iam/issues/238 --- .../BusinessLogic/ClientScopeMapperUpdater.cs | 38 +++--- .../BusinessLogic/ClientScopesUpdater.cs | 64 +++++----- .../BusinessLogic/IdentityProvidersUpdater.cs | 37 +++--- .../BusinessLogic/LocalizationsUpdater.cs | 13 ++- .../BusinessLogic/RolesUpdater.cs | 62 +++++----- .../BusinessLogic/UsersUpdater.cs | 109 ++++++++++-------- 6 files changed, 177 insertions(+), 146 deletions(-) diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs index 1593d9fb1e..5f2fe6dcc3 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs @@ -37,27 +37,31 @@ public async Task UpdateClientScopeMapper(string instanceName, CancellationToken var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.ClientScopes); var clients = await keycloak.GetClientsAsync(realm, null, true, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - foreach (var (clientName, mappingModels) in seedDataHandler.ClientScopeMappings) - { - var client = clients.SingleOrDefault(x => x.ClientId == clientName); - if (client?.Id is null) + await Parallel.ForEachAsync(seedDataHandler.ClientScopeMappings, + new ParallelOptions { MaxDegreeOfParallelism = 3, CancellationToken = cancellationToken }, + async (mappings, token) => { - throw new ConflictException($"No client id found with name {clientName}"); - } - - var roles = await keycloak.GetRolesAsync(realm, client.Id, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - foreach (var mappingModel in mappingModels) - { - var clientScope = clients.SingleOrDefault(x => x.ClientId == mappingModel.Client); - if (clientScope?.Id is null) + var (clientName, mappingModels) = mappings; + var client = clients.SingleOrDefault(x => x.ClientId == clientName); + if (client?.Id is null) { throw new ConflictException($"No client id found with name {clientName}"); } - var clientRoles = await keycloak.GetClientRolesScopeMappingsForClientAsync(realm, clientScope.Id, client.Id, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - var mappingModelRoles = mappingModel.Roles?.Select(roleName => roles.SingleOrDefault(r => r.Name == roleName) ?? throw new ConflictException($"No role with name {roleName} found")) ?? Enumerable.Empty(); - await AddAndDeleteRoles(keycloak, realm, clientScope.Id, client.Id, clientRoles, mappingModelRoles, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - } - } + + var roles = await keycloak.GetRolesAsync(realm, client.Id, cancellationToken: token).ConfigureAwait(ConfigureAwaitOptions.None); + foreach (var mappingModel in mappingModels) + { + var clientScope = clients.SingleOrDefault(x => x.ClientId == mappingModel.Client); + if (clientScope?.Id is null) + { + throw new ConflictException($"No client id found with name {clientName}"); + } + + var clientRoles = await keycloak.GetClientRolesScopeMappingsForClientAsync(realm, clientScope.Id, client.Id, token).ConfigureAwait(ConfigureAwaitOptions.None); + var mappingModelRoles = mappingModel.Roles?.Select(roleName => roles.SingleOrDefault(r => r.Name == roleName) ?? throw new ConflictException($"No role with name {roleName} found")) ?? Enumerable.Empty(); + await AddAndDeleteRoles(keycloak, realm, clientScope.Id, client.Id, clientRoles, mappingModelRoles, seederConfig, token).ConfigureAwait(ConfigureAwaitOptions.None); + } + }).ConfigureAwait(ConfigureAwaitOptions.None); } private static async Task AddAndDeleteRoles(KeycloakClient keycloak, string realm, string clientScopeId, string clientId, IEnumerable roles, IEnumerable updateRoles, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs index a9ecfef54d..7d08a516d7 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs @@ -39,51 +39,59 @@ public async Task UpdateClientScopes(string instanceName, CancellationToken canc var clientScopes = await keycloak.GetClientScopesAsync(realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); var seedClientScopes = seedDataHandler.ClientScopes; - await CheckAndExecute(ModificationType.Delete, keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken, RemoveObsoleteClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecute(ModificationType.Create, keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken, CreateMissingClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecute(ModificationType.Update, keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken, UpdateExistingClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = 3, CancellationToken = cancellationToken }; + await CheckAndExecute(ModificationType.Delete, keycloak, realm, clientScopes, seedClientScopes, seederConfig, parallelOptions, RemoveObsoleteClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecute(ModificationType.Create, keycloak, realm, clientScopes, seedClientScopes, seederConfig, parallelOptions, CreateMissingClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecute(ModificationType.Update, keycloak, realm, clientScopes, seedClientScopes, seederConfig, parallelOptions, UpdateExistingClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); } - private static Task CheckAndExecute(ModificationType modificationType, KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken, Func, IEnumerable, KeycloakSeederConfigModel, CancellationToken, Task> executeLogic) => + private static Task CheckAndExecute(ModificationType modificationType, KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakSeederConfigModel seederConfig, ParallelOptions parallelOptions, Func, IEnumerable, KeycloakSeederConfigModel, ParallelOptions, Task> executeLogic) => seederConfig.ModificationAllowed(modificationType) - ? executeLogic(keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken) + ? executeLogic(keycloak, realm, clientScopes, seedClientScopes, seederConfig, parallelOptions) : Task.CompletedTask; - private static async Task RemoveObsoleteClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) + private static async Task RemoveObsoleteClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakSeederConfigModel seederConfig, ParallelOptions parallelOptions) { - foreach (var deleteScope in clientScopes - .Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, x.Name)) - .ExceptBy(seedClientScopes.Select(x => x.Name), x => x.Name)) - { - await keycloak.DeleteClientScopeAsync( - realm, - deleteScope.Id ?? throw new ConflictException($"clientScope.Id is null: {deleteScope.Name}"), - cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - } + var deleteScopes = clientScopes + .Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, x.Name)) + .ExceptBy(seedClientScopes.Select(x => x.Name), x => x.Name); + await Parallel.ForEachAsync(deleteScopes, parallelOptions, async (scope, cancellationToken) => + { + await keycloak.DeleteClientScopeAsync( + realm, + scope.Id ?? throw new ConflictException($"clientScope.Id is null: {scope.Name}"), + cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + }) + .ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task CreateMissingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) + private static async Task CreateMissingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakSeederConfigModel seederConfig, ParallelOptions parallelOptions) { - foreach (var addScope in seedClientScopes - .Where(x => seederConfig.ModificationAllowed(ModificationType.Create, x.Name)) - .ExceptBy(clientScopes.Select(x => x.Name), x => x.Name)) - { - await keycloak.CreateClientScopeAsync(realm, CreateClientScope(null, addScope, true), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - } + var clientScopeModels = seedClientScopes + .Where(x => seederConfig.ModificationAllowed(ModificationType.Create, x.Name)) + .ExceptBy(clientScopes.Select(x => x.Name), x => x.Name); + await Parallel.ForEachAsync(clientScopeModels, parallelOptions, async (scope, cancellationToken) => + { + await keycloak.CreateClientScopeAsync(realm, CreateClientScope(null, scope, true), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + }) + .ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task UpdateExistingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) + private static async Task UpdateExistingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakSeederConfigModel seederConfig, ParallelOptions parallelOptions) { - foreach (var (clientScope, update) in clientScopes + var clientScopeModels = clientScopes .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x.Name)) .Join( seedClientScopes, x => x.Name, x => x.Name, - (clientScope, update) => (ClientScope: clientScope, Update: update))) - { - await UpdateClientScopeWithProtocolMappers(keycloak, realm, clientScope, update, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - } + (clientScope, update) => (ClientScope: clientScope, Update: update)); + await Parallel.ForEachAsync(clientScopeModels, parallelOptions, async (scope, cancellationToken) => + { + var (clientScope, update) = scope; + await UpdateClientScopeWithProtocolMappers(keycloak, realm, clientScope, update, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + }) + .ConfigureAwait(ConfigureAwaitOptions.None); } private static async Task UpdateClientScopeWithProtocolMappers(KeycloakClient keycloak, string realm, ClientScope clientScope, ClientScopeModel update, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs index 63cbabc6e7..7ded6be673 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs @@ -36,7 +36,8 @@ public async Task UpdateIdentityProviders(string keycloakInstanceName, Cancellat var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); var realm = seedDataHandler.Realm; var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.IdentityProviders); - + var parallelOptions = new ParallelOptions { CancellationToken = cancellationToken, MaxDegreeOfParallelism = 3 }; + foreach (var updateIdentityProvider in seedDataHandler.IdentityProviders) { if (updateIdentityProvider.Alias == null) @@ -64,17 +65,19 @@ public async Task UpdateIdentityProviders(string keycloakInstanceName, Cancellat var updateMappers = seedDataHandler.IdentityProviderMappers.Where(x => x.IdentityProviderAlias == updateIdentityProvider.Alias); var mappers = await keycloak.GetIdentityProviderMappersAsync(realm, updateIdentityProvider.Alias, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await DeleteObsoleteIdentityProviderMappers(keycloak, realm, updateIdentityProvider.Alias, mappers, updateMappers, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CreateMissingIdentityProviderMappers(keycloak, realm, updateIdentityProvider.Alias, mappers, updateMappers, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await UpdateExistingIdentityProviderMappers(keycloak, realm, updateIdentityProvider.Alias, mappers, updateMappers, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await DeleteObsoleteIdentityProviderMappers(keycloak, realm, updateIdentityProvider.Alias, mappers, updateMappers, seederConfig, parallelOptions).ConfigureAwait(ConfigureAwaitOptions.None); + await CreateMissingIdentityProviderMappers(keycloak, realm, updateIdentityProvider.Alias, mappers, updateMappers, seederConfig, parallelOptions).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateExistingIdentityProviderMappers(keycloak, realm, updateIdentityProvider.Alias, mappers, updateMappers, seederConfig, parallelOptions).ConfigureAwait(ConfigureAwaitOptions.None); } } - private static async Task CreateMissingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) + private static async Task CreateMissingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, KeycloakSeederConfigModel seederConfig, ParallelOptions parallelOptions) { - foreach (var mapper in updateMappers - .Where(x => seederConfig.ModificationAllowed(alias, ConfigurationKey.IdentityProviderMappers, ModificationType.Create, x.Name)) - .ExceptBy(mappers.Select(x => x.Name), x => x.Name)) + var addMappers = updateMappers + .Where(x => seederConfig.ModificationAllowed(alias, ConfigurationKey.IdentityProviderMappers, + ModificationType.Create, x.Name)) + .ExceptBy(mappers.Select(x => x.Name), x => x.Name); + await Parallel.ForEachAsync(addMappers, parallelOptions, async (mapper, cancellationToken) => { await keycloak.AddIdentityProviderMapperAsync( realm, @@ -87,44 +90,46 @@ await keycloak.AddIdentityProviderMapperAsync( }, mapper), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - } + }).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task UpdateExistingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) + private static async Task UpdateExistingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, KeycloakSeederConfigModel seederConfig, ParallelOptions parallelOptions) { - foreach (var (mapper, update) in mappers + var mappersToUpdate = mappers .Where(x => seederConfig.ModificationAllowed(alias, ConfigurationKey.IdentityProviderMappers, ModificationType.Update, x.Name)) .Join( updateMappers, x => x.Name, x => x.Name, (mapper, update) => (Mapper: mapper, Update: update)) - .Where(x => !CompareIdentityProviderMapper(x.Mapper, x.Update))) + .Where(x => !CompareIdentityProviderMapper(x.Mapper, x.Update)); + await Parallel.ForEachAsync(mappersToUpdate, parallelOptions, async (mapperModel, cancellationToken) => { + var (mapper, update) = mapperModel; await keycloak.UpdateIdentityProviderMapperAsync( realm, alias, mapper.Id ?? throw new ConflictException($"identityProviderMapper.id must never be null {mapper.Name} {mapper.IdentityProviderAlias}"), UpdateIdentityProviderMapper(mapper, update), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - } + }).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task DeleteObsoleteIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) + private static async Task DeleteObsoleteIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, KeycloakSeederConfigModel seederConfig, ParallelOptions parallelOptions) { if (mappers .Where(x => seederConfig.ModificationAllowed(alias, ConfigurationKey.IdentityProviderMappers, ModificationType.Delete, x.Name)) .ExceptBy(updateMappers.Select(x => x.Name), x => x.Name) .IfAny(async deleteMappers => { - foreach (var mapper in deleteMappers) + await Parallel.ForEachAsync(deleteMappers, parallelOptions, async (mapper, cancellationToken) => { await keycloak.DeleteIdentityProviderMapperAsync( realm, alias, mapper.Id ?? throw new ConflictException($"identityProviderMapper.id must never be null {mapper.Name} {mapper.IdentityProviderAlias}"), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - } + }).ConfigureAwait(ConfigureAwaitOptions.None); }, out var deleteMappersTask)) { diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs index b867723bed..449e71f5ea 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs @@ -35,14 +35,15 @@ public async Task UpdateLocalizations(string keycloakInstanceName, CancellationT var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.Localizations); var localizations = await keycloak.GetLocaleAsync(realm, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); var updateRealmLocalizations = seedDataHandler.RealmLocalizations; - await UpdateLocaleTranslations(keycloak, realm, localizations, updateRealmLocalizations, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - foreach (var deleteTranslation in localizations - .Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, x)) - .ExceptBy(updateRealmLocalizations.Select(t => t.Locale), locale => locale)) + + var deleteTranslations = localizations + .Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, x)) + .ExceptBy(updateRealmLocalizations.Select(t => t.Locale), locale => locale); + await Parallel.ForEachAsync(deleteTranslations, new ParallelOptions { CancellationToken = cancellationToken, MaxDegreeOfParallelism = 3 }, async (deleteTranslation, ct) => { - await keycloak.DeleteLocaleAsync(realm, deleteTranslation, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - } + await keycloak.DeleteLocaleAsync(realm, deleteTranslation, ct).ConfigureAwait(ConfigureAwaitOptions.None); + }).ConfigureAwait(ConfigureAwaitOptions.None); } private static async Task UpdateLocaleTranslations(KeycloakClient keycloak, string realm, IEnumerable locales, diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs index 304c58b83d..86688651c8 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs @@ -35,20 +35,22 @@ public async Task UpdateClientRoles(string keycloakInstanceName, CancellationTok var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); var realm = seedDataHandler.Realm; var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.ClientRoles); + var parallelOptions = new ParallelOptions { CancellationToken = cancellationToken, MaxDegreeOfParallelism = 3 }; foreach (var (clientId, updateRoles) in seedDataHandler.ClientRoles) { var id = seedDataHandler.GetIdOfClient(clientId); var roles = await keycloak.GetRolesAsync(realm, id, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - foreach (var newRole in updateRoles - .Where(x => seederConfig.ModificationAllowed(ModificationType.Create, x.Name)) - .ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name)) + var newRoles = updateRoles + .Where(x => seederConfig.ModificationAllowed(ModificationType.Create, x.Name)) + .ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name); + await Parallel.ForEachAsync(newRoles, parallelOptions, async (newRole, ct) => { - await keycloak.CreateRoleAsync(realm, id, CreateRole(newRole), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - } + await keycloak.CreateRoleAsync(realm, id, CreateRole(newRole), ct).ConfigureAwait(ConfigureAwaitOptions.None); + }).ConfigureAwait(ConfigureAwaitOptions.None); - await UpdateAndDeleteRoles(keycloak, realm, roles, updateRoles, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateAndDeleteRoles(keycloak, realm, roles, updateRoles, seederConfig, parallelOptions).ConfigureAwait(ConfigureAwaitOptions.None); } } @@ -59,28 +61,31 @@ public async Task UpdateRealmRoles(string keycloakInstanceName, CancellationToke var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.Roles); var roles = await keycloak.GetRolesAsync(realm, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); var updateRealmRoles = seedDataHandler.RealmRoles; + var parallelOptions = new ParallelOptions { CancellationToken = cancellationToken, MaxDegreeOfParallelism = 3 }; - foreach (var newRole in updateRealmRoles - .Where(x => seederConfig.ModificationAllowed(ModificationType.Create, x.Name)) - .ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name)) + var newRoles = updateRealmRoles + .Where(x => seederConfig.ModificationAllowed(ModificationType.Create, x.Name)) + .ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name); + await Parallel.ForEachAsync(newRoles, parallelOptions, async (newRole, ct) => { - await keycloak.CreateRoleAsync(realm, CreateRole(newRole), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - } + await keycloak.CreateRoleAsync(realm, CreateRole(newRole), ct).ConfigureAwait(ConfigureAwaitOptions.None); + }).ConfigureAwait(ConfigureAwaitOptions.None); - await UpdateAndDeleteRoles(keycloak, realm, roles, updateRealmRoles, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateAndDeleteRoles(keycloak, realm, roles, updateRealmRoles, seederConfig, parallelOptions).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task UpdateAndDeleteRoles(KeycloakClient keycloak, string realm, IEnumerable roles, IEnumerable updateRoles, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) + private static async Task UpdateAndDeleteRoles(KeycloakClient keycloak, string realm, IEnumerable roles, IEnumerable updateRoles, KeycloakSeederConfigModel seederConfig, ParallelOptions parallelOptions) { - foreach (var (role, update) in - roles - .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x.Name)) - .Join( - updateRoles, - role => role.Name, - roleModel => roleModel.Name, - (role, roleModel) => (Role: role, Update: roleModel))) + var rolesToUpdate = roles + .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x.Name)) + .Join( + updateRoles, + role => role.Name, + roleModel => roleModel.Name, + (role, roleModel) => (Role: role, Update: roleModel)); + await Parallel.ForEachAsync(rolesToUpdate, parallelOptions, async (entity, ct) => { + var (role, update) = entity; if (!CompareRole(role, update)) { if (role.Id == null) @@ -88,19 +93,20 @@ private static async Task UpdateAndDeleteRoles(KeycloakClient keycloak, string r if (role.ContainerId == null) throw new ConflictException($"role containerId must not be null: {role.Name}"); - await keycloak.UpdateRoleByIdAsync(realm, role.Id, CreateUpdateRole(role.Id, role.ContainerId, update), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await keycloak.UpdateRoleByIdAsync(realm, role.Id, CreateUpdateRole(role.Id, role.ContainerId, update), ct).ConfigureAwait(ConfigureAwaitOptions.None); } - } + }).ConfigureAwait(ConfigureAwaitOptions.None); - foreach (var deleteRole in roles - .Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, x.Name)) - .ExceptBy(updateRoles.Select(roleModel => roleModel.Name), role => role.Name)) + var deleteRoles = roles + .Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, x.Name)) + .ExceptBy(updateRoles.Select(roleModel => roleModel.Name), role => role.Name); + await Parallel.ForEachAsync(deleteRoles, parallelOptions, async (deleteRole, ct) => { if (deleteRole.Id == null) throw new ConflictException($"role id must not be null: {deleteRole.Name}"); - await keycloak.DeleteRoleByIdAsync(realm, deleteRole.Id, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - } + await keycloak.DeleteRoleByIdAsync(realm, deleteRole.Id, ct).ConfigureAwait(ConfigureAwaitOptions.None); + }).ConfigureAwait(ConfigureAwaitOptions.None); } public async Task UpdateCompositeRoles(string keycloakInstanceName, CancellationToken cancellationToken) diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs index af712e3304..b6cd09fed7 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs @@ -39,40 +39,42 @@ public async Task UpdateUsers(string keycloakInstanceName, CancellationToken can var clientsDictionary = seedDataHandler.ClientsDictionary; var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.Users); - foreach (var seedUser in seedDataHandler.Users) - { - if (seedUser.Username == null) - throw new ConflictException($"username must not be null {seedUser.Id}"); - - var createAllowed = seederConfig.ModificationAllowed(ModificationType.Create, seedUser.Username); - var updateAllowed = seederConfig.ModificationAllowed(ModificationType.Update, seedUser.Username); - if (!createAllowed && !updateAllowed) + await Parallel.ForEachAsync(seedDataHandler.Users, + new ParallelOptions { CancellationToken = cancellationToken, MaxDegreeOfParallelism = 3 }, + async (seedUser, token) => { - continue; - } + if (seedUser.Username == null) + throw new ConflictException($"username must not be null {seedUser.Id}"); - var user = (await keycloak.GetUsersAsync(realm, username: seedUser.Username, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)).SingleOrDefault(x => x.UserName == seedUser.Username); + var createAllowed = seederConfig.ModificationAllowed(ModificationType.Create, seedUser.Username); + var updateAllowed = seederConfig.ModificationAllowed(ModificationType.Update, seedUser.Username); + if (!createAllowed && !updateAllowed) + { + return; + } - if (user == null && createAllowed) - { - var result = await keycloak.RealmPartialImportAsync(realm, CreatePartialImportUser(seedUser), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - if (result.Overwritten != 0 || result.Added != 1 || result.Skipped != 0) + var user = (await keycloak.GetUsersAsync(realm, username: seedUser.Username, cancellationToken: token).ConfigureAwait(ConfigureAwaitOptions.None)).SingleOrDefault(x => x.UserName == seedUser.Username); + + if (user == null && createAllowed) { - throw new ConflictException($"PartialImport failed to add user id: {seedUser.Id}, userName: {seedUser.Username}"); + var result = await keycloak.RealmPartialImportAsync(realm, CreatePartialImportUser(seedUser), token).ConfigureAwait(ConfigureAwaitOptions.None); + if (result.Overwritten != 0 || result.Added != 1 || result.Skipped != 0) + { + throw new ConflictException($"PartialImport failed to add user id: {seedUser.Id}, userName: {seedUser.Username}"); + } } - } - else if (user != null && updateAllowed) - { - await UpdateUser( - keycloak, - realm, - user, - seedUser, - clientsDictionary, - seederConfig, - cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - } - } + else if (user != null && updateAllowed) + { + await UpdateUser( + keycloak, + realm, + user, + seedUser, + clientsDictionary, + seederConfig, + token).ConfigureAwait(ConfigureAwaitOptions.None); + } + }).ConfigureAwait(ConfigureAwaitOptions.None); } private static async Task UpdateUser(KeycloakClient keycloak, string realm, User user, UserModel seedUser, IReadOnlyDictionary clientsDictionary, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) @@ -219,57 +221,62 @@ private static bool CompareFederatedIdentity(FederatedIdentity identity, Federat private static async Task UpdateFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable updates, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { + var parallelOptions = new ParallelOptions { CancellationToken = cancellationToken, MaxDegreeOfParallelism = 3 }; var identities = await keycloak.GetUserSocialLoginsAsync(realm, userId).ConfigureAwait(ConfigureAwaitOptions.None); - await DeleteObsoleteFederatedIdentities(keycloak, realm, username, userId, identities, updates, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CreateMissingFederatedIdentities(keycloak, realm, username, userId, identities, updates, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await UpdateExistingFederatedIdentities(keycloak, realm, username, userId, identities, updates, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await DeleteObsoleteFederatedIdentities(keycloak, realm, username, userId, identities, updates, seederConfig, parallelOptions).ConfigureAwait(ConfigureAwaitOptions.None); + await CreateMissingFederatedIdentities(keycloak, realm, username, userId, identities, updates, seederConfig, parallelOptions).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateExistingFederatedIdentities(keycloak, realm, username, userId, identities, updates, seederConfig, parallelOptions).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task DeleteObsoleteFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable identities, IEnumerable updates, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) + private static async Task DeleteObsoleteFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable identities, IEnumerable updates, KeycloakSeederConfigModel seederConfig, ParallelOptions parallelOptions) { - foreach (var identity in identities - .Where(x => seederConfig.ModificationAllowed(username, ConfigurationKey.FederatedIdentities, ModificationType.Delete, x.IdentityProvider)) - .ExceptBy(updates.Select(x => x.IdentityProvider), x => x.IdentityProvider)) + var fisToRemove = identities + .Where(x => seederConfig.ModificationAllowed(username, ConfigurationKey.FederatedIdentities, ModificationType.Delete, x.IdentityProvider)) + .ExceptBy(updates.Select(x => x.IdentityProvider), x => x.IdentityProvider); + await Parallel.ForEachAsync(fisToRemove, parallelOptions, async (identity, cancellationToken) => { await keycloak.RemoveUserSocialLoginProviderAsync( realm, userId, identity.IdentityProvider ?? throw new ConflictException($"federatedIdentity.IdentityProvider is null {userId}"), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - } + }).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task CreateMissingFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable identities, IEnumerable updates, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) + private static async Task CreateMissingFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable identities, IEnumerable updates, KeycloakSeederConfigModel seederConfig, ParallelOptions parallelOptions) { - foreach (var update in updates - .Where(x => seederConfig.ModificationAllowed(username, ConfigurationKey.FederatedIdentities, ModificationType.Create, x.IdentityProvider)) - .ExceptBy(identities.Select(x => x.IdentityProvider), x => x.IdentityProvider)) + var fisToCreate = updates + .Where(x => seederConfig.ModificationAllowed(username, ConfigurationKey.FederatedIdentities, ModificationType.Create, x.IdentityProvider)) + .ExceptBy(identities.Select(x => x.IdentityProvider), x => x.IdentityProvider); + await Parallel.ForEachAsync(fisToCreate, parallelOptions, async (identity, cancellationToken) => { await keycloak.AddUserSocialLoginProviderAsync( realm, userId, - update.IdentityProvider ?? throw new ConflictException($"federatedIdentity.IdentityProvider is null {userId}"), + identity.IdentityProvider ?? throw new ConflictException($"federatedIdentity.IdentityProvider is null {userId}"), new() { - IdentityProvider = update.IdentityProvider, - UserId = update.UserId ?? throw new ConflictException($"federatedIdentity.UserId is null {userId}, {update.IdentityProvider}"), - UserName = update.UserName ?? throw new ConflictException($"federatedIdentity.UserName is null {userId}, {update.IdentityProvider}") + IdentityProvider = identity.IdentityProvider, + UserId = identity.UserId ?? throw new ConflictException($"federatedIdentity.UserId is null {userId}, {identity.IdentityProvider}"), + UserName = identity.UserName ?? throw new ConflictException($"federatedIdentity.UserName is null {userId}, {identity.IdentityProvider}") }, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - } + }).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task UpdateExistingFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable identities, IEnumerable updates, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) - { - foreach (var (identity, update) in identities + private static async Task UpdateExistingFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable identities, IEnumerable updates, KeycloakSeederConfigModel seederConfig, ParallelOptions parallelOptions) + { + var fisToUpdate = identities .Where(x => seederConfig.ModificationAllowed(username, ConfigurationKey.FederatedIdentities, ModificationType.Update, x.IdentityProvider)) .Join( updates, x => x.IdentityProvider, x => x.IdentityProvider, (identity, update) => (Identity: identity, Update: update)) - .Where(x => !CompareFederatedIdentity(x.Identity, x.Update))) + .Where(x => !CompareFederatedIdentity(x.Identity, x.Update)); + await Parallel.ForEachAsync(fisToUpdate, parallelOptions, async (identityModel, cancellationToken) => { + var (identity, update) = identityModel; await keycloak.RemoveUserSocialLoginProviderAsync( realm, userId, @@ -287,7 +294,7 @@ await keycloak.AddUserSocialLoginProviderAsync( UserName = update.UserName ?? throw new ConflictException($"federatedIdentity.UserName is null {userId}, {update.IdentityProvider}") }, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - } + }).ConfigureAwait(ConfigureAwaitOptions.None); } private static Credentials CreateUpdateCredentials(CredentialsModel update) =>