From 001292c780adc53e175470dee3ad6b138956bd56 Mon Sep 17 00:00:00 2001 From: desand01 Date: Sat, 17 Feb 2024 06:31:45 -0500 Subject: [PATCH] Fixes #1226 - keycloak_client detects changes on check_mode but not in run mode (#7881) * Fix warning integrated * Update Keycloak version intergrated test * Exclude metadata from diff test * Sanity * Add fragments * typo * Add test * Update changelogs/fragments/7881-fix-keycloak-client-ckeckmode.yml Co-authored-by: Felix Fontein * Remove docker compose * Update changelogs/fragments/7881-fix-keycloak-client-ckeckmode.yml Co-authored-by: Felix Fontein --------- Co-authored-by: Andre Desrosiers Co-authored-by: Felix Fontein --- .../7881-fix-keycloak-client-ckeckmode.yml | 2 + plugins/modules/keycloak_client.py | 5 +- .../targets/keycloak_client/README.md | 20 +-- .../keycloak_client/docker-compose.yml | 31 ----- .../targets/keycloak_client/tasks/main.yml | 120 ++++++++++++------ .../targets/keycloak_client/vars/main.yml | 2 +- 6 files changed, 98 insertions(+), 82 deletions(-) create mode 100644 changelogs/fragments/7881-fix-keycloak-client-ckeckmode.yml delete mode 100644 tests/integration/targets/keycloak_client/docker-compose.yml diff --git a/changelogs/fragments/7881-fix-keycloak-client-ckeckmode.yml b/changelogs/fragments/7881-fix-keycloak-client-ckeckmode.yml new file mode 100644 index 0000000000..485950c11c --- /dev/null +++ b/changelogs/fragments/7881-fix-keycloak-client-ckeckmode.yml @@ -0,0 +1,2 @@ +bugfixes: + - keycloak_client - fixes issue when metadata is provided in desired state when task is in check mode (https://github.com/ansible-collections/community.general/issues/1226, https://github.com/ansible-collections/community.general/pull/7881). \ No newline at end of file diff --git a/plugins/modules/keycloak_client.py b/plugins/modules/keycloak_client.py index 870578138b..b151e4541f 100644 --- a/plugins/modules/keycloak_client.py +++ b/plugins/modules/keycloak_client.py @@ -717,13 +717,14 @@ end_state: ''' from ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, camel, \ - keycloak_argument_spec, get_token, KeycloakError + keycloak_argument_spec, get_token, KeycloakError, is_struct_included from ansible.module_utils.basic import AnsibleModule import copy PROTOCOL_OPENID_CONNECT = 'openid-connect' PROTOCOL_SAML = 'saml' +CLIENT_META_DATA = ['authorizationServicesEnabled'] def normalise_cr(clientrep, remove_ids=False): @@ -946,7 +947,7 @@ def main(): if module._diff: result['diff'] = dict(before=sanitize_cr(before_norm), after=sanitize_cr(desired_norm)) - result['changed'] = (before_norm != desired_norm) + result['changed'] = not is_struct_included(desired_norm, before_norm, CLIENT_META_DATA) module.exit_json(**result) diff --git a/tests/integration/targets/keycloak_client/README.md b/tests/integration/targets/keycloak_client/README.md index d8bcc08ecc..f2b1012aa8 100644 --- a/tests/integration/targets/keycloak_client/README.md +++ b/tests/integration/targets/keycloak_client/README.md @@ -4,14 +4,16 @@ GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://w SPDX-License-Identifier: GPL-3.0-or-later --> -The integration test can be performed as follows: +# Running keycloak_client module integration test -``` -# 1. Start docker-compose: -docker-compose -f tests/integration/targets/keycloak_client/docker-compose.yml stop -docker-compose -f tests/integration/targets/keycloak_client/docker-compose.yml rm -f -v -docker-compose -f tests/integration/targets/keycloak_client/docker-compose.yml up -d +To run Keycloak client module's integration test, start a keycloak server using Docker: -# 2. Run the integration tests: -ansible-test integration keycloak_client --allow-unsupported -v -``` + docker run -d --rm --name mykeycloak -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=password quay.io/keycloak/keycloak:latest start-dev --http-relative-path /auth + +Run the integration tests: + + ansible-test integration -v keycloak_client --allow-unsupported --docker fedora35 --docker-network host + +Cleanup: + + docker stop mykeycloak diff --git a/tests/integration/targets/keycloak_client/docker-compose.yml b/tests/integration/targets/keycloak_client/docker-compose.yml deleted file mode 100644 index 5e14e9aac1..0000000000 --- a/tests/integration/targets/keycloak_client/docker-compose.yml +++ /dev/null @@ -1,31 +0,0 @@ ---- -# Copyright (c) Ansible Project -# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) -# SPDX-License-Identifier: GPL-3.0-or-later - -version: '3.4' - -services: - postgres: - image: postgres:9.6 - restart: always - environment: - POSTGRES_USER: postgres - POSTGRES_DB: postgres - POSTGRES_PASSWORD: postgres - - keycloak: - image: jboss/keycloak:12.0.4 - ports: - - 8080:8080 - - environment: - DB_VENDOR: postgres - DB_ADDR: postgres - DB_DATABASE: postgres - DB_USER: postgres - DB_SCHEMA: public - DB_PASSWORD: postgres - - KEYCLOAK_USER: admin - KEYCLOAK_PASSWORD: password diff --git a/tests/integration/targets/keycloak_client/tasks/main.yml b/tests/integration/targets/keycloak_client/tasks/main.yml index 513d5836b8..5e7c7fae39 100644 --- a/tests/integration/targets/keycloak_client/tasks/main.yml +++ b/tests/integration/targets/keycloak_client/tasks/main.yml @@ -2,58 +2,78 @@ # Copyright (c) Ansible Project # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later +- name: Wait for Keycloak + uri: + url: "{{ url }}/admin/" + status_code: 200 + validate_certs: no + register: result + until: result.status == 200 + retries: 10 + delay: 10 - name: Delete realm - community.general.keycloak_realm: "{{ auth_args | combine(call_args) }}" - vars: - call_args: - id: "{{ realm }}" - realm: "{{ realm }}" - state: absent + community.general.keycloak_realm: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + id: "{{ realm }}" + realm: "{{ realm }}" + state: absent - name: Create realm - community.general.keycloak_realm: "{{ auth_args | combine(call_args) }}" - vars: - call_args: - id: "{{ realm }}" - realm: "{{ realm }}" - state: present + community.general.keycloak_realm: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + id: "{{ realm }}" + realm: "{{ realm }}" + state: present - name: Desire client - community.general.keycloak_client: "{{ auth_args | combine(call_args) }}" - vars: - call_args: - realm: "{{ realm }}" - client_id: "{{ client_id }}" - state: present - redirect_uris: '{{redirect_uris1}}' - attributes: '{{client_attributes1}}' - protocol_mappers: '{{protocol_mappers1}}' + community.general.keycloak_client: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_id }}" + state: present + redirect_uris: '{{redirect_uris1}}' + attributes: '{{client_attributes1}}' + protocol_mappers: '{{protocol_mappers1}}' register: desire_client_not_present - name: Desire client again with same props - community.general.keycloak_client: "{{ auth_args | combine(call_args) }}" - vars: - call_args: - realm: "{{ realm }}" - client_id: "{{ client_id }}" - state: present - redirect_uris: '{{redirect_uris1}}' - attributes: '{{client_attributes1}}' - protocol_mappers: '{{protocol_mappers1}}' + community.general.keycloak_client: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_id }}" + state: present + redirect_uris: '{{redirect_uris1}}' + attributes: '{{client_attributes1}}' + protocol_mappers: '{{protocol_mappers1}}' register: desire_client_when_present_and_same - name: Check client again with same props - community.general.keycloak_client: "{{ auth_args | combine(call_args) }}" + community.general.keycloak_client: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_id }}" + state: present + redirect_uris: '{{redirect_uris1}}' + attributes: '{{client_attributes1}}' + protocol_mappers: '{{protocol_mappers1}}' + authorization_services_enabled: False check_mode: true - vars: - call_args: - realm: "{{ realm }}" - client_id: "{{ client_id }}" - state: present - redirect_uris: '{{redirect_uris1}}' - attributes: '{{client_attributes1}}' - protocol_mappers: '{{protocol_mappers1}}' register: check_client_when_present_and_same - name: Assert changes not detected in last two tasks (desire when same, and check) @@ -61,3 +81,25 @@ that: - desire_client_when_present_and_same is not changed - check_client_when_present_and_same is not changed + +- name: Check client again with changed props + community.general.keycloak_client: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_id }}" + state: present + redirect_uris: '{{redirect_uris1}}' + attributes: '{{client_attributes1}}' + protocol_mappers: '{{protocol_mappers1}}' + authorization_services_enabled: False + service_accounts_enabled: True + check_mode: true + register: check_client_when_present_and_changed + +- name: Assert changes detected in last tasks + assert: + that: + - check_client_when_present_and_changed is changed diff --git a/tests/integration/targets/keycloak_client/vars/main.yml b/tests/integration/targets/keycloak_client/vars/main.yml index 53ba35fcad..498f93e709 100644 --- a/tests/integration/targets/keycloak_client/vars/main.yml +++ b/tests/integration/targets/keycloak_client/vars/main.yml @@ -24,7 +24,7 @@ redirect_uris1: - "http://example.b.com/" - "http://example.a.com/" -client_attributes1: {"backchannel.logout.session.required": true, "backchannel.logout.revoke.offline.tokens": false} +client_attributes1: {"backchannel.logout.session.required": true, "backchannel.logout.revoke.offline.tokens": false, "client.secret.creation.time": 0} protocol_mappers1: - name: 'email'