diff --git a/changelogs/fragments/11503-keycloak-group-search-optimization.yml b/changelogs/fragments/11503-keycloak-group-search-optimization.yml new file mode 100644 index 0000000000..0f7dda2fc6 --- /dev/null +++ b/changelogs/fragments/11503-keycloak-group-search-optimization.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - keycloak_client_rolemapping, keycloak_realm_rolemapping, keycloak_group - optimize retrieval of groups by name to use Keycloak search API with exact matching instead of fetching all groups (https://github.com/ansible-collections/community.general/pull/11503). diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index e27c7b3fb5..a636ec30aa 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -1637,9 +1637,8 @@ class KeycloakAPI: def get_group_by_name(self, name, realm: str = "master", parents=None): """Fetch a keycloak group within a realm based on its name. - The Keycloak API does not allow filtering of the Groups resource by name. - As a result, this method first retrieves the entire list of groups - name and ID - - then performs a second query to fetch the group. + Uses the Keycloak search API with exact matching for efficient lookup + instead of fetching all groups. If the group does not exist, None is returned. :param name: Name of the group to fetch. @@ -1653,11 +1652,21 @@ class KeycloakAPI: if not parent: return None - all_groups = self.get_subgroups(parent, realm) + # For subgroups: use children endpoint with search parameter + search_url = "{url}?search={name}&exact=true".format( + url=URL_GROUP_CHILDREN.format(url=self.baseurl, realm=realm, groupid=parent["id"]), + name=quote(name, safe=""), + ) else: - all_groups = self.get_groups(realm=realm) + # For top-level groups: use groups endpoint with search parameter + search_url = "{url}?search={name}&exact=true".format( + url=URL_GROUPS.format(url=self.baseurl, realm=realm), name=quote(name, safe="") + ) - for group in all_groups: + groups = self._request_and_deserialize(search_url, method="GET") + + # exact=true should return only exact matches, but verify the name + for group in groups: if group["name"] == name: return self.get_group_by_groupid(group["id"], realm=realm)