- name: "Update REALM settings (merge LDAP component .config)" include_tasks: _update.yml vars: kc_object_kind: "component" kc_lookup_value: "{{ KEYCLOAK_LDAP_CMP_NAME }}" kc_desired: >- {{ ( KEYCLOAK_DICTIONARY_REALM.components['org.keycloak.storage.UserStorageProvider'] | selectattr('name','equalto', KEYCLOAK_LDAP_CMP_NAME) | list | first ) }} kc_merge_path: "config" - name: Resolve LDAP component id shell: > {{ KEYCLOAK_EXEC_KCADM }} get components -r {{ KEYCLOAK_REALM }} --query 'name={{ KEYCLOAK_LDAP_CMP_NAME }}' --fields id --format json | jq -r '.[0].id' register: ldap_cmp_id changed_when: false - name: Assert LDAP component id resolved assert: that: [ "(ldap_cmp_id.stdout | trim) not in ['', 'null']" ] fail_msg: "LDAP component '{{ KEYCLOAK_LDAP_CMP_NAME }}' not found in Keycloak." - name: Pull LDAP component from dictionary (by name) set_fact: ldap_component_tpl: >- {{ KEYCLOAK_DICTIONARY_REALM.components['org.keycloak.storage.UserStorageProvider'] | selectattr('name','equalto', KEYCLOAK_LDAP_CMP_NAME) | list | first | default({}) }} - name: Sanity check dictionary LDAP component assert: that: - ldap_component_tpl | length > 0 - (ldap_component_tpl.subComponents | default({})) | length > 0 fail_msg: "LDAP component '{{ KEYCLOAK_LDAP_CMP_NAME }}' not found in KEYCLOAK_DICTIONARY_REALM." - name: Extract mapper 'ldap-roles' from template (raw) set_fact: desired_group_mapper_raw: >- {{ ( ldap_component_tpl.subComponents['org.keycloak.storage.ldap.mappers.LDAPStorageMapper'] | default([]) ) | selectattr('name','equalto','ldap-roles') | list | first | default({}) }} - name: Ensure mapper exists in dictionary assert: that: [ "desired_group_mapper_raw | length > 0" ] fail_msg: "'ldap-roles' mapper not found in dictionary under LDAP component." - name: Build clean mapper payload set_fact: desired_group_mapper: >- {{ desired_group_mapper_raw | dict2items | rejectattr('key','in',['subComponents','id']) | list | items2dict }} - name: Extract desired groups.path (if any) set_fact: desired_groups_path: "{{ (desired_group_mapper.config['groups.path'] | default([])) | first | default('') | trim }}" desired_groups_top: "{{ ( (desired_group_mapper.config['groups.path'] | default([])) | first | default('') | trim ).lstrip('/') | split('/') | first | default('') }}" changed_when: false - name: Resolve existing top-level group id for groups.path when: desired_groups_top | length > 0 shell: > {{ KEYCLOAK_EXEC_KCADM }} get groups -r {{ KEYCLOAK_REALM }} --format json | jq -r '.[] | select(.name=="{{ desired_groups_top }}") | .id' | head -n1 register: groups_top_id changed_when: false - name: Create top-level group for groups.path if missing when: - desired_groups_top | length > 0 - (groups_top_id.stdout | trim) in ["", "null"] shell: > {{ KEYCLOAK_EXEC_KCADM }} create groups -r {{ KEYCLOAK_REALM }} -s name={{ desired_groups_top }} register: create_groups_top changed_when: create_groups_top.rc == 0 failed_when: create_groups_top.rc != 0 and ('already exists' not in (create_groups_top.stderr | lower)) - name: Find existing 'ldap-roles' mapper under LDAP component shell: > {{ KEYCLOAK_EXEC_KCADM }} get components -r {{ KEYCLOAK_REALM }} --query "parent={{ ldap_cmp_id.stdout | trim }}&type=org.keycloak.storage.ldap.mappers.LDAPStorageMapper&name=ldap-roles" --format json | jq -r '.[] | select(.parentId=="{{ ldap_cmp_id.stdout | trim }}" and .name=="ldap-roles") | .id' | head -n1 register: grp_mapper_id changed_when: false - name: Create 'ldap-roles' mapper if missing when: (grp_mapper_id.stdout | trim) in ["", "null"] shell: | cat <<'JSON' | {{ KEYCLOAK_EXEC_KCADM }} create components -r {{ KEYCLOAK_REALM }} -f - {{ desired_group_mapper | combine({ 'name': 'ldap-roles', 'parentId': ldap_cmp_id.stdout | trim, 'providerType': 'org.keycloak.storage.ldap.mappers.LDAPStorageMapper', 'providerId': 'group-ldap-mapper' }, recursive=True) | to_json }} JSON register: create_mapper changed_when: create_mapper.rc == 0 failed_when: create_mapper.rc != 0 and ('already exists' not in (create_mapper.stderr | lower)) no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" - name: Update 'ldap-roles' mapper config (merge only .config) when: (grp_mapper_id.stdout | trim) not in ["", "null"] vars: kc_object_kind: "component" kc_lookup_field: "id" kc_lookup_value: "{{ grp_mapper_id.stdout | trim }}" kc_desired: >- {{ desired_group_mapper | combine({ 'name': 'ldap-roles', 'parentId': ldap_cmp_id.stdout | trim, 'providerType': 'org.keycloak.storage.ldap.mappers.LDAPStorageMapper', 'providerId': 'group-ldap-mapper' }, recursive=True) }} kc_merge_path: "config" include_tasks: _update.yml