From f363d36a36beb1362ee2bb52d85136d5a1d39ae6 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Sun, 30 Nov 2025 02:03:06 +0100 Subject: [PATCH] Keycloak UserProfile: Switch to component lookup via providerId and use structured KEYCLOAK_USER_PROFILE_CONFIG_PAYLOAD for merging kc.user.profile.config. Simplifies update logic and removes jq-based ID resolution. See: https://chatgpt.com/share/692b97b2-7350-800f-9c2e-2672612c3b98 --- .../tasks/update/07_userprofile.yml | 45 ++++--------------- roles/web-app-keycloak/vars/main.yml | 22 +++++++++ 2 files changed, 31 insertions(+), 36 deletions(-) diff --git a/roles/web-app-keycloak/tasks/update/07_userprofile.yml b/roles/web-app-keycloak/tasks/update/07_userprofile.yml index 75d0e739..87932386 100644 --- a/roles/web-app-keycloak/tasks/update/07_userprofile.yml +++ b/roles/web-app-keycloak/tasks/update/07_userprofile.yml @@ -2,47 +2,20 @@ # Update the Declarative User Profile provider using the configuration # from KEYCLOAK_DICTIONARY_REALM.components (same pattern as LDAP). -- name: "Extract UserProfileProvider component from realm dictionary" - set_fact: - kc_userprofile_tpl: >- - {{ - KEYCLOAK_DICTIONARY_REALM.components['org.keycloak.userprofile.UserProfileProvider'] - | list | first | default({}) - }} - -- name: "Sanity check: UserProfileProvider exists in dictionary" - assert: - that: - - kc_userprofile_tpl | length > 0 - fail_msg: "UserProfileProvider component not found in KEYCLOAK_DICTIONARY_REALM." - when: MODE_ASSERT | bool - -- name: "Resolve UserProfileProvider component ID" - shell: > - {{ KEYCLOAK_EXEC_KCADM }} get components - -r {{ KEYCLOAK_REALM }} --format json - | jq -r ' - map( - select( - .providerType == "org.keycloak.userprofile.UserProfileProvider" - and .providerId == "declarative-user-profile" - ) - ) - | .[0].id // "" - ' - register: kc_userprofile_id - changed_when: false - - name: "Update UserProfileProvider component (merge kc.user.profile.config)" keycloak_kcadm_update: - object_kind: "component" - lookup_field: "id" - lookup_value: "{{ kc_userprofile_id.stdout | trim }}" - desired: "{{ kc_userprofile_tpl }}" + object_kind: "component" # <-- singular, as in your module choices + lookup_field: "providerId" # or "name", both are "declarative-user-profile" + lookup_value: "declarative-user-profile" + desired: "{{ KEYCLOAK_USER_PROFILE_CONFIG_PAYLOAD }}" # <-- REQUIRED merge_path: "config" kcadm_exec: "{{ KEYCLOAK_EXEC_KCADM }}" realm: "{{ KEYCLOAK_REALM }}" assert_mode: "{{ MODE_ASSERT }}" + force_attrs: + parentId: "{{ KEYCLOAK_REALM }}" + providerType: "org.keycloak.userprofile.UserProfileProvider" + providerId: "declarative-user-profile" no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}" - poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}" + poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}" diff --git a/roles/web-app-keycloak/vars/main.yml b/roles/web-app-keycloak/vars/main.yml index 4f4dab6f..5875e4da 100644 --- a/roles/web-app-keycloak/vars/main.yml +++ b/roles/web-app-keycloak/vars/main.yml @@ -94,3 +94,25 @@ KEYCLOAK_DICTIONARY_REALM: >- if (KEYCLOAK_DICTIONARY_REALM_RAW is mapping) else (KEYCLOAK_DICTIONARY_REALM_RAW | from_json) }} + +# Parsed user profile object from the realm template +KEYCLOAK_USER_PROFILE_OBJECT: >- + {{ + ( + KEYCLOAK_DICTIONARY_REALM.components['org.keycloak.userprofile.UserProfileProvider'] + | first + )['config']['kc.user.profile.config'][0] + | from_json + }} + +# Payload we actually want to merge into the component's config +KEYCLOAK_USER_PROFILE_CONFIG_PAYLOAD: >- + {{ + { + 'config': { + 'kc.user.profile.config': [ + KEYCLOAK_USER_PROFILE_OBJECT | to_json + ] + } + } + }} \ No newline at end of file