From 1f0a7c289f0796e1636f16e3da846b1ad05984b0 Mon Sep 17 00:00:00 2001 From: Thomas Bargetz Date: Fri, 24 Jul 2026 10:20:17 +0200 Subject: [PATCH 1/2] 368 introduce the parameter prune_undefined_realm_attributes to control whether undefined realm attributes are pruned --- plugins/modules/keycloak_realm.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/plugins/modules/keycloak_realm.py b/plugins/modules/keycloak_realm.py index 7eb3fbc..0418ccf 100644 --- a/plugins/modules/keycloak_realm.py +++ b/plugins/modules/keycloak_realm.py @@ -424,6 +424,13 @@ options: aliases: - permanentLockout type: bool + prune_undefined_realm_attributes: + description: + - If True all realm attributes which are not defined in the attributes dict will be deleted + aliases: + - pruneUndefinedRealmAttributes + type: bool + default: true quick_login_check_milli_seconds: description: - The realm quick login check in milliseconds. @@ -799,6 +806,9 @@ from ansible_collections.middleware_automation.keycloak.plugins.module_utils.ide get_token, keycloak_argument_spec, ) +from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak._keycloak_utils import ( + merge_settings_without_absent_nulls, +) def normalise_cr(realmrep): @@ -920,6 +930,7 @@ def main(): password_policy=dict(type="str", aliases=["passwordPolicy"], no_log=False), organizations_enabled=dict(type="bool", aliases=["organizationsEnabled"]), permanent_lockout=dict(type="bool", aliases=["permanentLockout"]), + prune_undefined_realm_attributes=dict(type="bool", aliases=["pruneUndefinedRealmAttributes"], default=True), quick_login_check_milli_seconds=dict(type="int", aliases=["quickLoginCheckMilliSeconds"]), refresh_token_max_reuse=dict(type="int", aliases=["refreshTokenMaxReuse"], no_log=False), registration_allowed=dict(type="bool", aliases=["registrationAllowed"]), @@ -1031,9 +1042,10 @@ def main(): realm = module.params.get("realm") state = module.params.get("state") + prune_undefined_realm_attributes = module.params.get("prune_undefined_realm_attributes") # convert module parameters to realm representation parameters (if they belong in there) - params_to_ignore = list(keycloak_argument_spec().keys()) + ["state"] + params_to_ignore = list(keycloak_argument_spec().keys()) + ["state", "prune_undefined_realm_attributes"] # Filter and map the parameters names that apply to the role realm_params = [x for x in module.params if x not in params_to_ignore and module.params.get(x) is not None] @@ -1049,6 +1061,9 @@ def main(): for realm_param in realm_params: new_param_value = module.params.get(realm_param) + if camel(realm_param) == "attributes" and not prune_undefined_realm_attributes: + new_param_value = merge_settings_without_absent_nulls(before_realm.get("attributes"), new_param_value) + changeset[camel(realm_param)] = new_param_value # Prepare the desired values using the existing values (non-existence results in a dict that is save to use as a basis) From 95c6fe1b50e4d4180076bf24b51a9d678649bb66 Mon Sep 17 00:00:00 2001 From: Thomas Bargetz Date: Mon, 27 Jul 2026 11:01:35 +0200 Subject: [PATCH 2/2] 386 add integration tests --- molecule/keycloak_modules/verify.yml | 56 ++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/molecule/keycloak_modules/verify.yml b/molecule/keycloak_modules/verify.yml index ca8c7e9..c112cdb 100644 --- a/molecule/keycloak_modules/verify.yml +++ b/molecule/keycloak_modules/verify.yml @@ -195,6 +195,62 @@ enabled: true state: present + - name: keycloak_realm — modify realm attributes without pruning undefined attributes. Step 1 - Introduce custom realm attributes + middleware_automation.keycloak.keycloak_realm: + id: "{{ ephemeral_realm }}" + realm: "{{ ephemeral_realm }}" + enabled: true + state: present + prune_undefined_realm_attributes: false + attributes: + custom-realm-attribute-1: 123 + custom-realm-attribute-2: true + register: custom_realm_attributes_result + + - name: Assert realm attribute without pruning undefined data result (Step 1) + ansible.builtin.assert: + that: + - custom_realm_attributes_result is changed + - custom_realm_attributes_result.end_state.attributes["custom-realm-attribute-1"] == "123" + - custom_realm_attributes_result.end_state.attributes["custom-realm-attribute-2"] == "true" + + - name: keycloak_realm — modify realm attributes without pruning undefined attributes. Step 2 - Verify unchanged custom realm attributes + middleware_automation.keycloak.keycloak_realm: + id: "{{ ephemeral_realm }}" + realm: "{{ ephemeral_realm }}" + enabled: true + state: present + prune_undefined_realm_attributes: false + attributes: + custom-realm-attribute-2: false + register: custom_realm_attributes_result + + - name: Assert realm attribute without pruning undefined data result (Step 2) + ansible.builtin.assert: + that: + - custom_realm_attributes_result is changed + - custom_realm_attributes_result.end_state.attributes["custom-realm-attribute-1"] == "123" # should still be 123 + - custom_realm_attributes_result.end_state.attributes["custom-realm-attribute-2"] == "false" + + - name: keycloak_realm — modify realm attributes with pruning undefined attributes + middleware_automation.keycloak.keycloak_realm: + id: "{{ ephemeral_realm }}" + realm: "{{ ephemeral_realm }}" + enabled: true + state: present + prune_undefined_realm_attributes: true + attributes: + custom-realm-attribute-3: custom-attribute-3 + register: custom_realm_attributes_result + + - name: Assert realm attribute with pruning undefined data result + ansible.builtin.assert: + that: + - custom_realm_attributes_result is changed + - custom_realm_attributes_result.end_state.attributes["custom-realm-attribute-1"] is not defined + - custom_realm_attributes_result.end_state.attributes["custom-realm-attribute-2"] is not defined + - custom_realm_attributes_result.end_state.attributes["custom-realm-attribute-3"] == "custom-attribute-3" + - name: keycloak_realm_localization — set locale override middleware_automation.keycloak.keycloak_realm_localization: parent_id: "{{ target_realm }}"