From 001292c780adc53e175470dee3ad6b138956bd56 Mon Sep 17 00:00:00 2001
From: desand01 <desrosiers.a@hotmail.com>
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 <felix@fontein.de>

* Remove docker compose

* Update changelogs/fragments/7881-fix-keycloak-client-ckeckmode.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Andre Desrosiers <andre.desrosiers@ssss.gouv.qc.ca>
Co-authored-by: Felix Fontein <felix@fontein.de>
---
 .../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'