mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-08-30 07:18:09 +02:00
keycloak(role): add realm support to generic updater
- Allow kc_object_kind='realm' - Map endpoint to 'realms' and default lookup_field to 'id' - Use realm-specific kcadm GET/UPDATE (no -r flag) - Preserve immutables: id, realm - Guard query-based ID resolution to non-realm objects Context: fixing failure in 'Update REALM mail settings' task. See: https://chatgpt.com/share/68affdb8-3d28-800f-8480-aa6a74000bf8
This commit is contained in:
@@ -1,20 +1,20 @@
|
|||||||
# Generic updater for Keycloak client/component via kcadm.
|
# Generic updater for Keycloak client/component/realm via kcadm.
|
||||||
# Flow: resolve ID → read current object → merge with desired → preserve immutable fields → update via stdin.
|
# Flow: resolve ID → read current object → merge with desired → preserve immutable fields → update via stdin.
|
||||||
#
|
#
|
||||||
# Required vars (pass via include):
|
# Required vars (pass via include):
|
||||||
# - kc_object_kind: "client" | "component"
|
# - kc_object_kind: "client" | "component" | "client-scope" | "realm"
|
||||||
# - kc_lookup_value: e.g., KEYCLOAK_CLIENT_ID or KEYCLOAK_LDAP_CMP_NAME
|
# - kc_lookup_value: e.g., KEYCLOAK_CLIENT_ID or KEYCLOAK_LDAP_CMP_NAME or KEYCLOAK_REALM
|
||||||
# - kc_desired: dict, e.g., KEYCLOAK_DICTIONARY_CLIENT or KEYCLOAK_DICTIONARY_LDAP
|
# - kc_desired: dict, e.g., KEYCLOAK_DICTIONARY_CLIENT or KEYCLOAK_DICTIONARY_LDAP
|
||||||
#
|
#
|
||||||
# Optional:
|
# Optional:
|
||||||
# - kc_lookup_field: override lookup field (defaults: clientId for client, name for component)
|
# - kc_lookup_field: override lookup field (defaults: clientId for client, name for component, id for realm)
|
||||||
# - kc_merge_path: if set (e.g. "config"), only that subkey is merged
|
# - kc_merge_path: if set (e.g. "config"), only that subkey is merged
|
||||||
# - kc_force_attrs: dict to force on the final payload (merged last)
|
# - kc_force_attrs: dict to force on the final payload (merged last)
|
||||||
|
|
||||||
- name: Assert required vars
|
- name: Assert required vars
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- kc_object_kind in ['client','component','client-scope']
|
- kc_object_kind in ['client','component','client-scope','realm']
|
||||||
- kc_lookup_value is defined
|
- kc_lookup_value is defined
|
||||||
- kc_desired is defined
|
- kc_desired is defined
|
||||||
fail_msg: "kc_object_kind, kc_lookup_value, kc_desired are required."
|
fail_msg: "kc_object_kind, kc_lookup_value, kc_desired are required."
|
||||||
@@ -26,11 +26,13 @@
|
|||||||
{{ 'clients' if kc_object_kind == 'client'
|
{{ 'clients' if kc_object_kind == 'client'
|
||||||
else 'components' if kc_object_kind == 'component'
|
else 'components' if kc_object_kind == 'component'
|
||||||
else 'client-scopes' if kc_object_kind == 'client-scope'
|
else 'client-scopes' if kc_object_kind == 'client-scope'
|
||||||
|
else 'realms' if kc_object_kind == 'realm'
|
||||||
else '' }}
|
else '' }}
|
||||||
kc_lookup_field_eff: >-
|
kc_lookup_field_eff: >-
|
||||||
{{ 'clientId' if kc_object_kind == 'client'
|
{{ 'clientId' if kc_object_kind == 'client'
|
||||||
else (kc_lookup_field | default('name')) if kc_object_kind == 'component'
|
else (kc_lookup_field | default('name')) if kc_object_kind == 'component'
|
||||||
else 'name' if kc_object_kind == 'client-scope'
|
else 'name' if kc_object_kind == 'client-scope'
|
||||||
|
else 'id' if kc_object_kind == 'realm'
|
||||||
else '' }}
|
else '' }}
|
||||||
|
|
||||||
- name: Resolve object id (direct when lookup_field is id)
|
- name: Resolve object id (direct when lookup_field is id)
|
||||||
@@ -39,7 +41,7 @@
|
|||||||
kc_obj_id: "{{ kc_lookup_value | string }}"
|
kc_obj_id: "{{ kc_lookup_value | string }}"
|
||||||
|
|
||||||
- name: Resolve object id via query
|
- name: Resolve object id via query
|
||||||
when: kc_lookup_field_eff != 'id'
|
when: kc_lookup_field_eff != 'id' and kc_object_kind != 'realm'
|
||||||
shell: >
|
shell: >
|
||||||
{% if kc_object_kind == 'client-scope' -%}
|
{% if kc_object_kind == 'client-scope' -%}
|
||||||
{{ KEYCLOAK_EXEC_KCADM }} get client-scopes -r {{ KEYCLOAK_REALM }} --format json
|
{{ KEYCLOAK_EXEC_KCADM }} get client-scopes -r {{ KEYCLOAK_REALM }} --format json
|
||||||
@@ -72,8 +74,11 @@
|
|||||||
|
|
||||||
- name: Read current object
|
- name: Read current object
|
||||||
shell: >
|
shell: >
|
||||||
{{ KEYCLOAK_EXEC_KCADM }} get {{ kc_api }}/{{ kc_obj_id }}
|
{% if kc_object_kind == 'realm' -%}
|
||||||
-r {{ KEYCLOAK_REALM }} --format json
|
{{ KEYCLOAK_EXEC_KCADM }} get {{ kc_api }}/{{ kc_obj_id }} --format json
|
||||||
|
{%- else -%}
|
||||||
|
{{ KEYCLOAK_EXEC_KCADM }} get {{ kc_api }}/{{ kc_obj_id }} -r {{ KEYCLOAK_REALM }} --format json
|
||||||
|
{%- endif %}
|
||||||
register: kc_cur
|
register: kc_cur
|
||||||
changed_when: false
|
changed_when: false
|
||||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||||
@@ -141,12 +146,23 @@
|
|||||||
}}
|
}}
|
||||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||||
|
|
||||||
# Preserve immutables for client-scope
|
|
||||||
- name: Preserve immutable fields for client-scope
|
- name: Preserve immutable fields for client-scope
|
||||||
when: kc_object_kind == 'client-scope'
|
when: kc_object_kind == 'client-scope'
|
||||||
set_fact:
|
set_fact:
|
||||||
desired_obj: "{{ desired_obj | combine({'id': cur_obj.id, 'name': cur_obj.name}, recursive=True) }}"
|
desired_obj: "{{ desired_obj | combine({'id': cur_obj.id, 'name': cur_obj.name}, recursive=True) }}"
|
||||||
|
|
||||||
|
- name: Preserve immutable fields for realm
|
||||||
|
when: kc_object_kind == 'realm'
|
||||||
|
set_fact:
|
||||||
|
desired_obj: >-
|
||||||
|
{{
|
||||||
|
desired_obj
|
||||||
|
| combine({
|
||||||
|
'id': cur_obj.id,
|
||||||
|
'realm': cur_obj.realm
|
||||||
|
}, recursive=True)
|
||||||
|
}}
|
||||||
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||||
|
|
||||||
# Optional forced attributes (e.g., frontchannelLogout)
|
# Optional forced attributes (e.g., frontchannelLogout)
|
||||||
- name: Apply forced attributes (optional)
|
- name: Apply forced attributes (optional)
|
||||||
@@ -156,8 +172,14 @@
|
|||||||
|
|
||||||
- name: Update object via stdin
|
- name: Update object via stdin
|
||||||
shell: |
|
shell: |
|
||||||
|
{% if kc_object_kind == 'realm' -%}
|
||||||
|
cat <<'JSON' | {{ KEYCLOAK_EXEC_KCADM }} update {{ kc_api }}/{{ kc_obj_id }} -f -
|
||||||
|
{{ desired_obj | to_json }}
|
||||||
|
JSON
|
||||||
|
{%- else -%}
|
||||||
cat <<'JSON' | {{ KEYCLOAK_EXEC_KCADM }} update {{ kc_api }}/{{ kc_obj_id }} -r {{ KEYCLOAK_REALM }} -f -
|
cat <<'JSON' | {{ KEYCLOAK_EXEC_KCADM }} update {{ kc_api }}/{{ kc_obj_id }} -r {{ KEYCLOAK_REALM }} -f -
|
||||||
{{ desired_obj | to_json }}
|
{{ desired_obj | to_json }}
|
||||||
JSON
|
JSON
|
||||||
|
{%- endif %}
|
||||||
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
|
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 }}"
|
||||||
|
Reference in New Issue
Block a user