Compare commits

..

1 Commits

Author SHA1 Message Date
Ranabir Chakraborty
6600082294 Providing correct rhbk version 2026-06-03 20:08:27 +05:30
115 changed files with 953 additions and 1306 deletions

View File

@@ -1,6 +1,4 @@
# .ansible-lint # .ansible-lint
profile: production
exclude_paths: exclude_paths:
- .cache/ - .cache/
- .github/ - .github/
@@ -38,5 +36,7 @@ skip_list:
- vars_should_not_be_used - vars_should_not_be_used
- file_is_small_enough - file_is_small_enough
- file_has_valid_name - file_has_valid_name
- name[template]
- var-naming[no-role-prefix]
use_default_rules: true use_default_rules: true

View File

@@ -22,4 +22,4 @@ jobs:
root_permission_varname: 'keycloak_install_requires_become' root_permission_varname: 'keycloak_install_requires_become'
debug_verbosity: "${{ github.event.inputs.debug_verbosity }}" debug_verbosity: "${{ github.event.inputs.debug_verbosity }}"
molecule_tests: >- molecule_tests: >-
[ "debian", "sso_test", "quarkus", "quarkus_ha", "quarkus_ha_remote", "quarkus_ha_26.4_below", "default", "quarkus_devmode", "quarkus_upgrade", "keycloak_modules" ] [ "debian", "quarkus", "quarkus_ha", "quarkus_ha_remote", "quarkus_ha_26.4_below", "default", "quarkus_devmode", "quarkus_upgrade", "keycloak_modules" ]

View File

@@ -6,32 +6,6 @@ middleware\_automation.keycloak Release Notes
This changelog describes changes after version 0.2.6. This changelog describes changes after version 0.2.6.
v3.0.10
=======
Major Changes
-------------
- Fixing linting issues `#357 <https://github.com/ansible-middleware/keycloak/pull/357>`_
Minor Changes
-------------
- AMW-571 keycloak_identity_provider hide_on_login_page not working `#356 <https://github.com/ansible-middleware/keycloak/pull/356>`_
- Fixing vars naming issue `#359 <https://github.com/ansible-middleware/keycloak/pull/359>`_
- Removing from __future__ import annotations broke runtime type hints using Sequence and union syntax, causing sanity import failures and molecule module errors `#358 <https://github.com/ansible-middleware/keycloak/pull/358>`_
v3.0.9
======
Bugfixes
--------
- AMW-551 Providing correct rhbk version `#344 <https://github.com/ansible-middleware/keycloak/pull/344>`_
v3.0.8
======
v3.0.7 v3.0.7
====== ======

View File

@@ -49,9 +49,9 @@ A requirement file is provided to install:
<!--start roles_paths --> <!--start roles_paths -->
### Included roles ### Included roles
* [`keycloak_quarkus`](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak_quarkus/README.md): role for installing keycloak (>= 19.0.0, quarkus based). * `keycloak_quarkus`: role for installing keycloak (>= 19.0.0, quarkus based).
* [`keycloak_realm`](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak_realm/README.md): role for configuring a realm, user federation(s), clients and users, in an installed service. * `keycloak_realm`: role for configuring a realm, user federation(s), clients and users, in an installed service.
* [`keycloak`](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak/README.md): role for installing legacy keycloak (<= 19.0, wildfly based). * `keycloak`: role for installing legacy keycloak (<= 19.0, wildfly based).
<!--end roles_paths --> <!--end roles_paths -->
@@ -71,7 +71,7 @@ All Keycloak administration modules from `community.general` are provided in thi
* `keycloak_client_rolemapping`: manage client role mappings for users and groups. * `keycloak_client_rolemapping`: manage client role mappings for users and groups.
* `keycloak_client_rolescope`: manage client role scope mappings. * `keycloak_client_rolescope`: manage client role scope mappings.
* `keycloak_client_scope`: manage client scopes and protocol mappers (replaces `community.general.keycloak_clientscope`). * `keycloak_client_scope`: manage client scopes and protocol mappers (replaces `community.general.keycloak_clientscope`).
* `keycloak_client_scope_type`: manage default and optional client scope assignments. * `keycloak_clientscope_type`: manage default and optional client scope assignments.
* `keycloak_clientsecret_info`: retrieve client secret information. * `keycloak_clientsecret_info`: retrieve client secret information.
* `keycloak_clientsecret_regenerate`: regenerate a client secret. * `keycloak_clientsecret_regenerate`: regenerate a client secret.
* `keycloak_clienttemplate`: manage legacy client templates. * `keycloak_clienttemplate`: manage legacy client templates.
@@ -155,11 +155,11 @@ Note: when deploying clustered configurations, all hosts belonging to the cluste
Execute the following command from the source root directory: Execute the following command from the source root directory:
```bash ```bash
ansible-playbook -i <ansible_hosts> playbooks/keycloak_realm.yml -e keycloak_realm_admin_password=<changeme> -e keycloak_realm_realm=test ansible-playbook -i <ansible_hosts> playbooks/keycloak_realm.yml -e keycloak_admin_password=<changeme> -e keycloak_realm=test
``` ```
- `keycloak_realm_admin_password` password for the administration console user account. - `keycloak_admin_password` password for the administration console user account.
- `keycloak_realm_realm` name of the realm to be created/used. - `keycloak_realm` name of the realm to be created/used.
- `ansible_hosts` is the inventory, below is an example inventory for deploying to localhost - `ansible_hosts` is the inventory, below is an example inventory for deploying to localhost
``` ```
@@ -220,5 +220,5 @@ For details on changes between versions, please see the [CHANGELOG](https://gith
Apache License v2.0 or later Apache License v2.0 or later
<!--start license --> <!--start license -->
See [LICENSE](https://github.com/ansible-middleware/keycloak/blob/main/LICENSE) to view the full text. See [LICENSE](LICENSE) to view the full text.
<!--end license --> <!--end license -->

View File

@@ -6,5 +6,4 @@ python3-netaddr [platform:rpm platform:dpkg]
python3-lxml [platform:rpm platform:dpkg] python3-lxml [platform:rpm platform:dpkg]
python3-jmespath [platform:rpm platform:dpkg] python3-jmespath [platform:rpm platform:dpkg]
python3-requests [platform:rpm platform:dpkg] python3-requests [platform:rpm platform:dpkg]
podman [platform:rpm platform:dpkg]

View File

@@ -674,30 +674,6 @@ releases:
- 276.yaml - 276.yaml
- 277.yaml - 277.yaml
release_date: '2025-05-02' release_date: '2025-05-02'
3.0.10:
changes:
major_changes:
- 'Fixing linting issues `#357 <https://github.com/ansible-middleware/keycloak/pull/357>`_
'
minor_changes:
- 'AMW-571 keycloak_identity_provider hide_on_login_page not working `#356 <https://github.com/ansible-middleware/keycloak/pull/356>`_
'
- 'Fixing vars naming issue `#359 <https://github.com/ansible-middleware/keycloak/pull/359>`_
'
- 'Removing from __future__ import annotations broke runtime type hints using
Sequence and union syntax, causing sanity import failures and molecule module
errors `#358 <https://github.com/ansible-middleware/keycloak/pull/358>`_
'
fragments:
- 356.yaml
- 357.yaml
- 358.yaml
- 359.yaml
release_date: '2026-06-25'
3.0.2: 3.0.2:
changes: changes:
bugfixes: bugfixes:
@@ -849,14 +825,3 @@ releases:
- 341.yaml - 341.yaml
- 343.yaml - 343.yaml
release_date: '2026-06-01' release_date: '2026-06-01'
3.0.8:
release_date: '2026-06-09'
3.0.9:
changes:
bugfixes:
- 'AMW-551 Providing correct rhbk version `#344 <https://github.com/ansible-middleware/keycloak/pull/344>`_
'
fragments:
- 344.yaml
release_date: '2026-06-11'

View File

@@ -44,6 +44,7 @@ extensions = [
'sphinx.ext.autodoc', 'sphinx.ext.autodoc',
'sphinx.ext.intersphinx', 'sphinx.ext.intersphinx',
'sphinx_antsibull_ext', 'sphinx_antsibull_ext',
'ansible_basic_sphinx_ext',
] ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.

View File

@@ -1,7 +1,10 @@
# ansible_basic_sphinx_ext still imports pkg_resources (removed in setuptools 82+).
setuptools>=70.0.0,<81.0.0
antsibull>=0.17.0 antsibull>=0.17.0
antsibull-docs antsibull-docs
antsibull-changelog antsibull-changelog
ansible-core>=2.16.0 ansible-core>=2.16.0
ansible-pygments ansible-pygments
sphinx-rtd-theme sphinx-rtd-theme
git+https://github.com/felixfontein/ansible-basic-sphinx-ext
myst-parser myst-parser

View File

@@ -1,7 +1,7 @@
--- ---
namespace: middleware_automation namespace: middleware_automation
name: keycloak name: keycloak
version: "3.0.10" version: "3.0.8"
readme: README.md readme: README.md
authors: authors:
- Romain Pelisse <rpelisse@redhat.com> - Romain Pelisse <rpelisse@redhat.com>

View File

@@ -14,8 +14,8 @@ action_groups:
- keycloak_client_rolemapping - keycloak_client_rolemapping
- keycloak_client_rolescope - keycloak_client_rolescope
- keycloak_client_scope - keycloak_client_scope
- keycloak_client_scope_type - keycloak_clientscope_type
- keycloak_client_scope_rolemappings - keycloak_clientscope_rolemappings
- keycloak_clientsecret_info - keycloak_clientsecret_info
- keycloak_clientsecret_regenerate - keycloak_clientsecret_regenerate
- keycloak_clienttemplate - keycloak_clienttemplate
@@ -44,19 +44,3 @@ plugin_routing:
warning_text: >- warning_text: >-
The module has been renamed to keycloak_client_scope for Keycloak 17+ (Quarkus). The module has been renamed to keycloak_client_scope for Keycloak 17+ (Quarkus).
Update playbooks to use middleware_automation.keycloak.keycloak_client_scope. Update playbooks to use middleware_automation.keycloak.keycloak_client_scope.
keycloak_clientscope_type:
redirect: middleware_automation.keycloak.keycloak_client_scope_type
deprecation:
removal_version: 5.0.0
warning_text: >-
The module has been renamed to keycloak_client_scope_type for Keycloak 17+ (Quarkus).
Update playbooks to use middleware_automation.keycloak.keycloak_client_scope_type.
keycloak_clientscope_rolemappings:
redirect: middleware_automation.keycloak.keycloak_client_scope_rolemappings
deprecation:
removal_version: 5.0.0
warning_text: >-
The module has been renamed to keycloak_client_scope_rolemappings for Keycloak 17+ (Quarkus).
Update playbooks to use middleware_automation.keycloak.keycloak_client_scope_rolemappings.

View File

@@ -14,32 +14,32 @@
roles: roles:
- role: keycloak_quarkus - role: keycloak_quarkus
- role: keycloak_realm - role: keycloak_realm
keycloak_realm_url: "{{ keycloak_quarkus_hostname }}" keycloak_url: "{{ keycloak_quarkus_hostname }}"
keycloak_realm_admin_user: "{{ keycloak_quarkus_bootstrap_admin_user }}" keycloak_admin_user: "{{ keycloak_quarkus_bootstrap_admin_user }}"
keycloak_realm_admin_password: "{{ keycloak_quarkus_bootstrap_admin_password }}" keycloak_admin_password: "{{ keycloak_quarkus_bootstrap_admin_password }}"
keycloak_realm_client_users: keycloak_client_users:
- username: TestUser - username: TestUser
password: password password: password
client_roles: client_roles:
- client: TestClient - client: TestClient
role: TestRoleUser role: TestRoleUser
realm: "{{ keycloak_realm_realm }}" realm: "{{ keycloak_realm }}"
- username: TestAdmin - username: TestAdmin
password: password password: password
client_roles: client_roles:
- client: TestClient - client: TestClient
role: TestRoleUser role: TestRoleUser
realm: "{{ keycloak_realm_realm }}" realm: "{{ keycloak_realm }}"
- client: TestClient - client: TestClient
role: TestRoleAdmin role: TestRoleAdmin
realm: "{{ keycloak_realm_realm }}" realm: "{{ keycloak_realm }}"
keycloak_realm_realm: TestRealm keycloak_realm: TestRealm
keycloak_realm_clients: keycloak_clients:
- name: TestClient - name: TestClient
realm: "{{ keycloak_realm_realm }}" realm: "{{ keycloak_realm }}"
public_client: "{{ keycloak_realm_client_public }}" public_client: "{{ keycloak_client_public }}"
web_origins: "{{ keycloak_realm_client_web_origins }}" web_origins: "{{ keycloak_client_web_origins }}"
users: "{{ keycloak_realm_client_users }}" users: "{{ keycloak_client_users }}"
client_id: TestClient client_id: TestClient
attributes: attributes:
post.logout.redirect.uris: '/public/logout' post.logout.redirect.uris: '/public/logout'

View File

@@ -19,30 +19,30 @@
roles: roles:
- role: keycloak_quarkus - role: keycloak_quarkus
- role: keycloak_realm - role: keycloak_realm
keycloak_realm_url: "{{ keycloak_quarkus_hostname }}" keycloak_url: "{{ keycloak_quarkus_hostname }}"
keycloak_realm_admin_user: "{{ keycloak_quarkus_bootstrap_admin_user }}" keycloak_admin_user: "{{ keycloak_quarkus_bootstrap_admin_user }}"
keycloak_realm_admin_password: "{{ keycloak_quarkus_bootstrap_admin_password }}" keycloak_admin_password: "{{ keycloak_quarkus_bootstrap_admin_password }}"
keycloak_realm_client_users: keycloak_client_users:
- username: TestUser - username: TestUser
password: password password: password
client_roles: client_roles:
- client: TestClient - client: TestClient
role: TestRoleUser role: TestRoleUser
realm: "{{ keycloak_realm_realm }}" realm: "{{ keycloak_realm }}"
- username: TestAdmin - username: TestAdmin
password: password password: password
client_roles: client_roles:
- client: TestClient - client: TestClient
role: TestRoleUser role: TestRoleUser
realm: "{{ keycloak_realm_realm }}" realm: "{{ keycloak_realm }}"
- client: TestClient - client: TestClient
role: TestRoleAdmin role: TestRoleAdmin
realm: "{{ keycloak_realm_realm }}" realm: "{{ keycloak_realm }}"
keycloak_realm_realm: TestRealm keycloak_realm: TestRealm
keycloak_realm_clients: keycloak_clients:
- name: TestClient - name: TestClient
realm: "{{ keycloak_realm_realm }}" realm: "{{ keycloak_realm }}"
public_client: "{{ keycloak_realm_client_public }}" public_client: "{{ keycloak_client_public }}"
web_origins: "{{ keycloak_realm_client_web_origins }}" web_origins: "{{ keycloak_client_web_origins }}"
users: "{{ keycloak_realm_client_users }}" users: "{{ keycloak_client_users }}"
client_id: TestClient client_id: TestClient

View File

@@ -1,6 +1,6 @@
--- ---
driver: driver:
name: docker name: podman
platforms: platforms:
- name: instance - name: instance
image: registry.access.redhat.com/ubi9/ubi-init:latest image: registry.access.redhat.com/ubi9/ubi-init:latest

View File

@@ -20,51 +20,8 @@
- name: Download keycloak archive to controller directory - name: Download keycloak archive to controller directory
ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user
url: https://github.com/keycloak/keycloak/releases/download/26.6.2/keycloak-26.6.2.zip url: https://github.com/keycloak/keycloak/releases/download/26.4.7/keycloak-26.4.7.zip
dest: /tmp/keycloak dest: /tmp/keycloak
mode: '0640' mode: '0640'
validate_certs: false
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
ignore_errors: true
- name: Attempt RHBK download using redhat.runtimes_common collection
when:
- rhn_username is defined
- rhn_username | length > 0
block:
- name: Retrieve RHBK product download using Unified Downloads API
middleware_automation.common.product_search:
client_id: "{{ rhn_username }}"
client_secret: "{{ rhn_password }}"
product_type: DISTRIBUTION
product_version: "{{ keycloak_quarkus_version | default('26.6.2') }}"
product_category: "RHBK"
register: rhn_products
no_log: "{{ omit_rhn_output | default(true) }}"
delegate_to: localhost
run_once: true
ignore_errors: true
- name: Determine install zipfile from search results
ansible.builtin.set_fact:
rhn_matched_products: "{{ rhn_products.results | selectattr('file_name', 'match', '.*keycloak-' + (keycloak_quarkus_version | default('26.6.2')) + '.zip$') }}"
delegate_to: localhost
run_once: true
when:
- rhn_products is defined
- rhn_products.results is defined
- name: Download Red Hat Build of Keycloak
middleware_automation.common.product_download:
client_id: "{{ rhn_username }}"
client_secret: "{{ rhn_password }}"
product_id: "{{ (rhn_matched_products | first).id }}"
dest: "/tmp/keycloak/keycloak-{{ keycloak_quarkus_version | default('26.6.2') }}.zip"
no_log: "{{ omit_rhn_output | default(true) }}"
delegate_to: localhost
run_once: true
when:
- rhn_matched_products is defined
- rhn_matched_products | length > 0
ignore_errors: true

View File

@@ -1,6 +1,6 @@
--- ---
driver: driver:
name: docker name: podman
platforms: platforms:
- name: instance - name: instance
image: registry.access.redhat.com/ubi9/ubi-init:latest image: registry.access.redhat.com/ubi9/ubi-init:latest

View File

@@ -37,8 +37,8 @@
- keycloak_client_rolemapping - keycloak_client_rolemapping
- keycloak_client_rolescope - keycloak_client_rolescope
- keycloak_client_scope - keycloak_client_scope
- keycloak_client_scope_type - keycloak_clientscope_type
- keycloak_client_scope_rolemappings - keycloak_clientscope_rolemappings
- keycloak_clientsecret_info - keycloak_clientsecret_info
- keycloak_clientsecret_regenerate - keycloak_clientsecret_regenerate
- keycloak_clienttemplate - keycloak_clienttemplate
@@ -253,50 +253,6 @@
clientId: molecule-idp-client clientId: molecule-idp-client
state: present state: present
- name: keycloak_identity_provider — set hide_on_login via top-level param
middleware_automation.keycloak.keycloak_identity_provider:
realm: "{{ target_realm }}"
alias: "{{ idp }}"
hide_on_login: true
state: present
register: idp_hide_on_login_result
- name: Assert hide_on_login is set correctly in end_state
ansible.builtin.assert:
that:
- idp_hide_on_login_result is changed
- idp_hide_on_login_result.end_state.hideOnLogin == true
- "'hide_on_login' not in (idp_hide_on_login_result.end_state.config | default({}))"
- name: keycloak_identity_provider — set hide_on_login idempotency check
middleware_automation.keycloak.keycloak_identity_provider:
realm: "{{ target_realm }}"
alias: "{{ idp }}"
hide_on_login: true
state: present
register: idp_hide_on_login_idempotent_result
- name: Assert hide_on_login idempotency (no change)
ansible.builtin.assert:
that:
- idp_hide_on_login_idempotent_result is not changed
- idp_hide_on_login_idempotent_result.end_state.hideOnLogin == true
- name: keycloak_identity_provider — set hide_on_login via legacy config key (backward compat)
middleware_automation.keycloak.keycloak_identity_provider:
realm: "{{ target_realm }}"
alias: "{{ idp }}"
hide_on_login: false
state: present
register: idp_hide_on_login_off_result
- name: Assert hide_on_login can be toggled off via top-level param
ansible.builtin.assert:
that:
- idp_hide_on_login_off_result is changed
- idp_hide_on_login_off_result.end_state.hideOnLogin == false
- "'hide_on_login_page' not in (idp_hide_on_login_off_result.end_state.config | default({}))"
- name: keycloak_clienttemplate — create client template - name: keycloak_clienttemplate — create client template
middleware_automation.keycloak.keycloak_clienttemplate: middleware_automation.keycloak.keycloak_clienttemplate:
realm: "{{ target_realm }}" realm: "{{ target_realm }}"
@@ -309,10 +265,10 @@
- "'404' not in (clienttemplate_result.msg | default(''))" - "'404' not in (clienttemplate_result.msg | default(''))"
- "'Not Found' not in (clienttemplate_result.msg | default(''))" - "'Not Found' not in (clienttemplate_result.msg | default(''))"
- name: keycloak_client_scope_type — attach scope as optional on realm - name: keycloak_clientscope_type — attach scope as optional on realm
middleware_automation.keycloak.keycloak_client_scope_type: middleware_automation.keycloak.keycloak_clientscope_type:
realm: "{{ target_realm }}" realm: "{{ target_realm }}"
optional_client_scopes: optional_clientscopes:
- "{{ scope }}" - "{{ scope }}"
- name: keycloak_user_rolemapping — assign realm role to user - name: keycloak_user_rolemapping — assign realm role to user
@@ -343,54 +299,54 @@
- name: keycloak_client_rolescope — restrict realm role on client - name: keycloak_client_rolescope — restrict realm role on client
middleware_automation.keycloak.keycloak_client_rolescope: middleware_automation.keycloak.keycloak_client_rolescope:
realm: "{{ target_realm }}" realm: "{{ target_realm }}"
target_client_id: "{{ client }}" client_id: "{{ client }}"
role_names: role_names:
- "{{ role }}" - "{{ role }}"
state: present state: present
- name: keycloak_client_scope_rolemappings — map client roles to client scope - name: keycloak_clientscope_rolemappings — map client roles to clientscope
middleware_automation.keycloak.keycloak_client_scope_rolemappings: middleware_automation.keycloak.keycloak_clientscope_rolemappings:
realm: "{{ target_realm }}" realm: "{{ target_realm }}"
client_id: "{{ client }}" client_id: "{{ client }}"
client_scope_id: "{{ scope }}" clientscope_id: "{{ scope }}"
role_names: role_names:
- "{{ client_role }}" - "{{ client_role }}"
register: client_scope_rolemappings_result register: clientscope_rolemappings_result
- name: Assert client scope role mappings were created - name: Assert clientscope role mappings were created
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- client_scope_rolemappings_result is changed - clientscope_rolemappings_result is changed
- client_scope_rolemappings_result.end_state | length == 1 - clientscope_rolemappings_result.end_state | length == 1
- name: keycloak_client_scope_rolemappings — remap client role (idempotency) - name: keycloak_clientscope_rolemappings — remap client role (idempotency)
middleware_automation.keycloak.keycloak_client_scope_rolemappings: middleware_automation.keycloak.keycloak_clientscope_rolemappings:
realm: "{{ target_realm }}" realm: "{{ target_realm }}"
client_id: "{{ client }}" client_id: "{{ client }}"
client_scope_id: "{{ scope }}" clientscope_id: "{{ scope }}"
role_names: role_names:
- "{{ client_role }}" - "{{ client_role }}"
register: client_scope_rolemappings_idempotent_result register: clientscope_rolemappings_idempotent_result
- name: Assert client scope role mappings are idempotent - name: Assert clientscope role mappings are idempotent
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- client_scope_rolemappings_idempotent_result is not changed - clientscope_rolemappings_idempotent_result is not changed
- client_scope_rolemappings_idempotent_result.end_state | length == 1 - clientscope_rolemappings_idempotent_result.end_state | length == 1
- name: keycloak_client_scope_rolemappings — map realm role to client scope - name: keycloak_clientscope_rolemappings — map realm role to clientscope
middleware_automation.keycloak.keycloak_client_scope_rolemappings: middleware_automation.keycloak.keycloak_clientscope_rolemappings:
realm: "{{ target_realm }}" realm: "{{ target_realm }}"
client_scope_id: "{{ scope }}" clientscope_id: "{{ scope }}"
role_names: role_names:
- "{{ role }}" - "{{ role }}"
register: client_scope_realm_rolemappings_result register: clientscope_realm_rolemappings_result
- name: Assert realm role was mapped to client_scope - name: Assert realm role was mapped to clientscope
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- client_scope_realm_rolemappings_result is changed - clientscope_realm_rolemappings_result is changed
- client_scope_realm_rolemappings_result.end_state | length == 1 - clientscope_realm_rolemappings_result.end_state | length == 1
- name: keycloak_user — set email_verified explicitly - name: keycloak_user — set email_verified explicitly
middleware_automation.keycloak.keycloak_user: middleware_automation.keycloak.keycloak_user:
@@ -561,19 +517,19 @@
name: "{{ authz_scope }}" name: "{{ authz_scope }}"
state: absent state: absent
- name: keycloak_client_scope_rolemappings — remove realm role from client scope - name: keycloak_clientscope_rolemappings — remove realm role from clientscope
middleware_automation.keycloak.keycloak_client_scope_rolemappings: middleware_automation.keycloak.keycloak_clientscope_rolemappings:
realm: "{{ target_realm }}" realm: "{{ target_realm }}"
client_scope_id: "{{ scope }}" clientscope_id: "{{ scope }}"
role_names: role_names:
- "{{ role }}" - "{{ role }}"
state: absent state: absent
- name: keycloak_client_scope_rolemappings — remove client role from client scope - name: keycloak_clientscope_rolemappings — remove client role from clientscope
middleware_automation.keycloak.keycloak_client_scope_rolemappings: middleware_automation.keycloak.keycloak_clientscope_rolemappings:
realm: "{{ target_realm }}" realm: "{{ target_realm }}"
client_id: "{{ client }}" client_id: "{{ client }}"
client_scope_id: "{{ scope }}" clientscope_id: "{{ scope }}"
role_names: role_names:
- "{{ client_role }}" - "{{ client_role }}"
state: absent state: absent
@@ -581,7 +537,7 @@
- name: keycloak_client_rolescope — remove role scope mapping - name: keycloak_client_rolescope — remove role scope mapping
middleware_automation.keycloak.keycloak_client_rolescope: middleware_automation.keycloak.keycloak_client_rolescope:
realm: "{{ target_realm }}" realm: "{{ target_realm }}"
target_client_id: "{{ client }}" client_id: "{{ client }}"
role_names: role_names:
- "{{ role }}" - "{{ role }}"
state: absent state: absent

View File

@@ -4,9 +4,7 @@
vars_files: vars_files:
- ../group_vars/all/vars.yml - ../group_vars/all/vars.yml
vars: vars:
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_admin_password: "remembertochangeme" keycloak_admin_password: "remembertochangeme"
keycloak_quarkus_hostname: "http://instance:8080"
keycloak_config_override_template: custom.xml.j2 keycloak_config_override_template: custom.xml.j2
keycloak_http_port: 8081 keycloak_http_port: 8081
keycloak_management_http_port: 19990 keycloak_management_http_port: 19990

View File

@@ -7,7 +7,7 @@
keycloak_quarkus_show_deprecation_warnings: false keycloak_quarkus_show_deprecation_warnings: false
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme" keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme" keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
keycloak_realm_realm: TestRealm keycloak_realm: TestRealm
keycloak_quarkus_hostname: https://instance:8443 keycloak_quarkus_hostname: https://instance:8443
keycloak_quarkus_log: file keycloak_quarkus_log: file
keycloak_quarkus_log_level: debug # needed for the verify step keycloak_quarkus_log_level: debug # needed for the verify step
@@ -39,16 +39,16 @@
- key: default-connection-pool-size - key: default-connection-pool-size
value: 10 value: 10
- id: spid-saml - id: spid-saml
url: https://github.com/italia/spid-keycloak-provider/releases/download/26.5.6/spid-provider.jar url: https://github.com/italia/spid-keycloak-provider/releases/download/24.0.2/spid-provider.jar
- id: spid-saml-w-checksum - id: spid-saml-w-checksum
url: https://github.com/italia/spid-keycloak-provider/releases/download/26.5.6/spid-provider.jar url: https://github.com/italia/spid-keycloak-provider/releases/download/24.0.2/spid-provider.jar
checksum: sha256:2ddafc389a5f017d8665bfdfa2f72b3784fc74b9f3a482e796fa89a5ba5cc95b checksum: sha256:fbb50e73739d7a6d35b5bff611b1c01668b29adf6f6259624b95e466a305f377
- id: keycloak-kerberos-federation - id: keycloak-kerberos-federation
maven: maven:
repository_url: https://repo1.maven.org/maven2/ # https://mvnrepository.com/artifact/org.keycloak/keycloak-kerberos-federation/24.0.4 repository_url: https://repo1.maven.org/maven2/ # https://mvnrepository.com/artifact/org.keycloak/keycloak-kerberos-federation/24.0.4
group_id: org.keycloak group_id: org.keycloak
artifact_id: keycloak-kerberos-federation artifact_id: keycloak-kerberos-federation
version: 26.6.3 # optional version: 26.6.2 # optional
# username: myUser # optional # username: myUser # optional
# password: myPAT # optional # password: myPAT # optional
# - id: my-static-theme # - id: my-static-theme
@@ -62,34 +62,34 @@
roles: roles:
- role: keycloak_quarkus - role: keycloak_quarkus
- role: keycloak_realm - role: keycloak_realm
keycloak_realm_url: http://instance:8080 keycloak_url: http://instance:8080
keycloak_realm_admin_user: "{{ keycloak_quarkus_bootstrap_admin_user }}" keycloak_admin_user: "{{ keycloak_quarkus_bootstrap_admin_user }}"
keycloak_realm_admin_password: "{{ keycloak_quarkus_bootstrap_admin_password }}" keycloak_admin_password: "{{ keycloak_quarkus_bootstrap_admin_password }}"
keycloak_realm_client_default_roles: keycloak_client_default_roles:
- TestRoleAdmin - TestRoleAdmin
- TestRoleUser - TestRoleUser
keycloak_realm_client_users: keycloak_client_users:
- username: TestUser - username: TestUser
password: password password: password
client_roles: client_roles:
- client: TestClient - client: TestClient
role: TestRoleUser role: TestRoleUser
realm: "{{ keycloak_realm_realm }}" realm: "{{ keycloak_realm }}"
- username: TestAdmin - username: TestAdmin
password: password password: password
client_roles: client_roles:
- client: TestClient - client: TestClient
role: TestRoleUser role: TestRoleUser
realm: "{{ keycloak_realm_realm }}" realm: "{{ keycloak_realm }}"
- client: TestClient - client: TestClient
role: TestRoleAdmin role: TestRoleAdmin
realm: "{{ keycloak_realm_realm }}" realm: "{{ keycloak_realm }}"
keycloak_realm_realm: TestRealm keycloak_realm: TestRealm
keycloak_realm_clients: keycloak_clients:
- name: TestClient - name: TestClient
roles: "{{ keycloak_realm_client_default_roles }}" roles: "{{ keycloak_client_default_roles }}"
realm: "{{ keycloak_realm_realm }}" realm: "{{ keycloak_realm }}"
public_client: "{{ keycloak_realm_client_public }}" public_client: "{{ keycloak_client_public }}"
web_origins: "{{ keycloak_realm_client_web_origins }}" web_origins: "{{ keycloak_client_web_origins }}"
users: "{{ keycloak_realm_client_users }}" users: "{{ keycloak_client_users }}"
client_id: TestClient client_id: TestClient

View File

@@ -7,7 +7,7 @@
keycloak_quarkus_show_deprecation_warnings: false keycloak_quarkus_show_deprecation_warnings: false
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme" keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme" keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
keycloak_realm_realm: TestRealm keycloak_realm: TestRealm
keycloak_quarkus_log: file keycloak_quarkus_log: file
keycloak_quarkus_hostname: 'http://localhost:8080' keycloak_quarkus_hostname: 'http://localhost:8080'
keycloak_quarkus_start_dev: True keycloak_quarkus_start_dev: True
@@ -18,34 +18,34 @@
roles: roles:
- role: keycloak_quarkus - role: keycloak_quarkus
- role: keycloak_realm - role: keycloak_realm
keycloak_realm_url: "{{ keycloak_quarkus_hostname }}" keycloak_url: "{{ keycloak_quarkus_hostname }}"
keycloak_realm_admin_user: "{{ keycloak_quarkus_bootstrap_admin_user }}" keycloak_admin_user: "{{ keycloak_quarkus_bootstrap_admin_user }}"
keycloak_realm_admin_password: "{{ keycloak_quarkus_bootstrap_admin_password }}" keycloak_admin_password: "{{ keycloak_quarkus_bootstrap_admin_password }}"
keycloak_realm_client_default_roles: keycloak_client_default_roles:
- TestRoleAdmin - TestRoleAdmin
- TestRoleUser - TestRoleUser
keycloak_realm_client_users: keycloak_client_users:
- username: TestUser - username: TestUser
password: password password: password
client_roles: client_roles:
- client: TestClient - client: TestClient
role: TestRoleUser role: TestRoleUser
realm: "{{ keycloak_realm_realm }}" realm: "{{ keycloak_realm }}"
- username: TestAdmin - username: TestAdmin
password: password password: password
client_roles: client_roles:
- client: TestClient - client: TestClient
role: TestRoleUser role: TestRoleUser
realm: "{{ keycloak_realm_realm }}" realm: "{{ keycloak_realm }}"
- client: TestClient - client: TestClient
role: TestRoleAdmin role: TestRoleAdmin
realm: "{{ keycloak_realm_realm }}" realm: "{{ keycloak_realm }}"
keycloak_realm_realm: TestRealm keycloak_realm: TestRealm
keycloak_realm_clients: keycloak_clients:
- name: TestClient - name: TestClient
roles: "{{ keycloak_realm_client_default_roles }}" roles: "{{ keycloak_client_default_roles }}"
realm: "{{ keycloak_realm_realm }}" realm: "{{ keycloak_realm }}"
public_client: "{{ keycloak_realm_client_public }}" public_client: "{{ keycloak_client_public }}"
web_origins: "{{ keycloak_realm_client_web_origins }}" web_origins: "{{ keycloak_client_web_origins }}"
users: "{{ keycloak_realm_client_users }}" users: "{{ keycloak_client_users }}"
client_id: TestClient client_id: TestClient

View File

@@ -1,6 +1,6 @@
--- ---
driver: driver:
name: docker name: podman
platforms: platforms:
- name: instance - name: instance
image: registry.access.redhat.com/ubi9/ubi-init:latest image: registry.access.redhat.com/ubi9/ubi-init:latest

View File

@@ -4,7 +4,7 @@ dependency:
options: options:
requirements-file: molecule/requirements.yml requirements-file: molecule/requirements.yml
driver: driver:
name: docker name: podman
platforms: platforms:
- name: instance - name: instance
image: registry.access.redhat.com/ubi9/ubi-init:latest image: registry.access.redhat.com/ubi9/ubi-init:latest

View File

@@ -1,22 +0,0 @@
---
- name: Converge
hosts: all
vars_files:
- ../group_vars/all/vars.yml
vars:
keycloak_admin_password: "remembertochangeme"
keycloak_enable: true
keycloak_offline_install: false
roles:
- role: keycloak
- role: keycloak_realm
keycloak_realm_url: "http://instance:8080"
keycloak_realm_context: "/auth"
keycloak_realm_admin_user: "admin"
keycloak_realm_admin_password: "remembertochangeme"
keycloak_realm_realm: TestRealm
keycloak_realm_clients:
- name: TestClient
realm: "{{ keycloak_realm_realm }}"
public_client: true
client_id: TestClient

View File

@@ -1,31 +0,0 @@
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: instance
image: registry.access.redhat.com/ubi9/ubi-init:latest
command: /usr/sbin/init
privileged: true
pre_build_image: true
provisioner:
name: ansible
config_options:
defaults:
interpreter_python: auto_silent
callbacks_enabled: profile_tasks, timer, yaml
ssh_connection:
pipelining: false
playbooks:
prepare: prepare.yml
converge: converge.yml
verify: verify.yml
env:
ANSIBLE_ROLES_PATH: "../../roles"
inventory:
host_vars:
instance:
ansible_user: root
verifier:
name: ansible

View File

@@ -1,11 +0,0 @@
---
- name: Prepare
hosts: all
vars_files:
- ../group_vars/all/vars.yml
gather_facts: yes
vars:
sudo_pkg_name: sudo
tasks:
- name: "Run preparation common to all scenario"
ansible.builtin.include_tasks: ../prepare.yml

View File

@@ -1,26 +0,0 @@
---
- name: Verify
hosts: all
vars:
keycloak_admin_password: "remembertochangeme"
keycloak_admin_user: "admin"
keycloak_uri: "http://localhost:8080"
keycloak_context: "/auth"
tasks:
- name: Populate service facts
ansible.builtin.service_facts:
- name: Check if keycloak service started
ansible.builtin.assert:
that:
- ansible_facts.services["keycloak.service"]["state"] == "running"
- ansible_facts.services["keycloak.service"]["status"] == "enabled"
- name: Verify token api call
ansible.builtin.uri:
url: "{{ keycloak_uri }}{{ keycloak_context }}/realms/master/protocol/openid-connect/token"
method: POST
body: "client_id=admin-cli&username={{ keycloak_admin_user }}&password={{ keycloak_admin_password }}&grant_type=password"
validate_certs: no
register: keycloak_auth_response
until: keycloak_auth_response.status == 200
retries: 2
delay: 2

View File

@@ -6,9 +6,9 @@
ansible.builtin.include_role: ansible.builtin.include_role:
name: keycloak_realm name: keycloak_realm
vars: vars:
keycloak_realm_admin_password: "remembertochangeme" keycloak_admin_password: "remembertochangeme"
keycloak_realm_realm: TestRealm keycloak_realm: TestRealm
keycloak_realm_user_federation: keycloak_user_federation:
- realm: TestRealm - realm: TestRealm
name: my-ldap name: my-ldap
provider_id: ldap provider_id: ldap
@@ -48,13 +48,13 @@
ldap.full.name.attribute: cn ldap.full.name.attribute: cn
read.only: true read.only: true
write.only: false write.only: false
keycloak_realm_clients: keycloak_clients:
- name: TestClient1 - name: TestClient1
client_id: TestClient1 client_id: TestClient1
roles: roles:
- TestClient1Admin - TestClient1Admin
- TestClient1User - TestClient1User
realm: "{{ keycloak_realm_realm }}" realm: "{{ keycloak_realm }}"
public_client: true public_client: true
web_origins: web_origins:
- http://testclient1origin/application - http://testclient1origin/application
@@ -65,4 +65,4 @@
client_roles: client_roles:
- client: TestClient1 - client: TestClient1
role: TestClient1User role: TestClient1User
realm: "{{ keycloak_realm_realm }}" realm: "{{ keycloak_realm }}"

View File

@@ -2,8 +2,8 @@
- name: Playbook for Keycloak Hosts - name: Playbook for Keycloak Hosts
hosts: all hosts: all
vars: vars:
keycloak_realm_admin_password: "remembertochangeme" keycloak_admin_password: "remembertochangeme"
keycloak_realm_clients: keycloak_clients:
- name: TestClient1 - name: TestClient1
client_id: TestClient1 client_id: TestClient1
roles: roles:
@@ -23,4 +23,4 @@
realm: TestRealm realm: TestRealm
roles: roles:
- role: middleware_automation.keycloak.keycloak_realm - role: middleware_automation.keycloak.keycloak_realm
keycloak_realm_realm: TestRealm keycloak_realm: TestRealm

View File

@@ -6,12 +6,12 @@
ansible.builtin.include_role: ansible.builtin.include_role:
name: middleware_automation.keycloak.keycloak_realm name: middleware_automation.keycloak.keycloak_realm
vars: vars:
keycloak_realm_admin_password: "remembertochangeme" keycloak_admin_password: "remembertochangeme"
keycloak_realm_realm: TestRealm keycloak_realm: TestRealm
keycloak_realm_client_default_roles: keycloak_client_default_roles:
- TestRoleAdmin - TestRoleAdmin
- TestRoleUser - TestRoleUser
keycloak_realm_client_users: keycloak_client_users:
- username: TestUser - username: TestUser
password: password password: password
client_roles: client_roles:
@@ -27,13 +27,13 @@
- client: TestClient1 - client: TestClient1
role: TestRoleAdmin role: TestRoleAdmin
realm: TestRealm realm: TestRealm
keycloak_realm_clients: keycloak_clients:
- name: TestClient1 - name: TestClient1
client_id: TestClient1 client_id: TestClient1
roles: "{{ keycloak_realm_client_default_roles }}" roles: "{{ keycloak_client_default_roles }}"
realm: TestRealm realm: TestRealm
public_client: true public_client: true
web_origins: web_origins:
- http://testclient1origin/application - http://testclient1origin/application
- http://testclient1origin/other - http://testclient1origin/other
users: "{{ keycloak_realm_client_users }}" users: "{{ keycloak_client_users }}"

View File

@@ -5,7 +5,6 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
from __future__ import annotations
__metaclass__ = type __metaclass__ = type

View File

@@ -5,7 +5,6 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
from __future__ import annotations
__metaclass__ = type __metaclass__ = type

View File

@@ -5,7 +5,6 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
from __future__ import annotations
__metaclass__ = type __metaclass__ = type

View File

@@ -5,9 +5,7 @@
# Note that this module util is **PRIVATE** to the collection. It can have breaking changes at any time. # Note that this module util is **PRIVATE** to the collection. It can have breaking changes at any time.
# Do not use this from other collections or standalone plugins/modules! # Do not use this from other collections or standalone plugins/modules!
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
import typing as t import typing as t

View File

@@ -2,9 +2,7 @@
# BSD 2-Clause license (see LICENSES/BSD-2-Clause.txt) # BSD 2-Clause license (see LICENSES/BSD-2-Clause.txt)
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
import copy import copy
import json import json
@@ -59,23 +57,23 @@ URL_GROUPS = "{url}/admin/realms/{realm}/groups"
URL_GROUP = "{url}/admin/realms/{realm}/groups/{groupid}" URL_GROUP = "{url}/admin/realms/{realm}/groups/{groupid}"
URL_GROUP_CHILDREN = "{url}/admin/realms/{realm}/groups/{groupid}/children" URL_GROUP_CHILDREN = "{url}/admin/realms/{realm}/groups/{groupid}/children"
URL_CLIENT_SCOPES = "{url}/admin/realms/{realm}/client-scopes" URL_CLIENTSCOPES = "{url}/admin/realms/{realm}/client-scopes"
URL_CLIENT_SCOPE = "{url}/admin/realms/{realm}/client-scopes/{id}" URL_CLIENTSCOPE = "{url}/admin/realms/{realm}/client-scopes/{id}"
URL_CLIENT_SCOPE_SCOPE_MAPPINGS = "{url}/admin/realms/{realm}/client-scopes/{id}/scope-mappings" URL_CLIENTSCOPE_SCOPE_MAPPINGS = "{url}/admin/realms/{realm}/client-scopes/{id}/scope-mappings"
URL_CLIENT_SCOPE_SCOPE_MAPPINGS_REALM = "{url}/admin/realms/{realm}/client-scopes/{id}/scope-mappings/realm" URL_CLIENTSCOPE_SCOPE_MAPPINGS_REALM = "{url}/admin/realms/{realm}/client-scopes/{id}/scope-mappings/realm"
URL_CLIENT_SCOPE_SCOPE_MAPPINGS_CLIENT = "{url}/admin/realms/{realm}/client-scopes/{id}/scope-mappings/clients/{client}" URL_CLIENTSCOPE_SCOPE_MAPPINGS_CLIENT = "{url}/admin/realms/{realm}/client-scopes/{id}/scope-mappings/clients/{client}"
URL_CLIENT_SCOPE_PROTOCOLMAPPERS = "{url}/admin/realms/{realm}/client-scopes/{id}/protocol-mappers/models" URL_CLIENTSCOPE_PROTOCOLMAPPERS = "{url}/admin/realms/{realm}/client-scopes/{id}/protocol-mappers/models"
URL_CLIENT_SCOPE_PROTOCOLMAPPER = "{url}/admin/realms/{realm}/client-scopes/{id}/protocol-mappers/models/{mapper_id}" URL_CLIENTSCOPE_PROTOCOLMAPPER = "{url}/admin/realms/{realm}/client-scopes/{id}/protocol-mappers/models/{mapper_id}"
URL_DEFAULT_CLIENT_SCOPES = "{url}/admin/realms/{realm}/default-default-client-scopes" URL_DEFAULT_CLIENTSCOPES = "{url}/admin/realms/{realm}/default-default-client-scopes"
URL_DEFAULT_CLIENT_SCOPE = "{url}/admin/realms/{realm}/default-default-client-scopes/{id}" URL_DEFAULT_CLIENTSCOPE = "{url}/admin/realms/{realm}/default-default-client-scopes/{id}"
URL_OPTIONAL_CLIENT_SCOPES = "{url}/admin/realms/{realm}/default-optional-client-scopes" URL_OPTIONAL_CLIENTSCOPES = "{url}/admin/realms/{realm}/default-optional-client-scopes"
URL_OPTIONAL_CLIENT_SCOPE = "{url}/admin/realms/{realm}/default-optional-client-scopes/{id}" URL_OPTIONAL_CLIENTSCOPE = "{url}/admin/realms/{realm}/default-optional-client-scopes/{id}"
URL_CLIENT_DEFAULT_CLIENT_SCOPES = "{url}/admin/realms/{realm}/clients/{cid}/default-client-scopes" URL_CLIENT_DEFAULT_CLIENTSCOPES = "{url}/admin/realms/{realm}/clients/{cid}/default-client-scopes"
URL_CLIENT_DEFAULT_CLIENT_SCOPE = "{url}/admin/realms/{realm}/clients/{cid}/default-client-scopes/{id}" URL_CLIENT_DEFAULT_CLIENTSCOPE = "{url}/admin/realms/{realm}/clients/{cid}/default-client-scopes/{id}"
URL_CLIENT_OPTIONAL_CLIENT_SCOPES = "{url}/admin/realms/{realm}/clients/{cid}/optional-client-scopes" URL_CLIENT_OPTIONAL_CLIENTSCOPES = "{url}/admin/realms/{realm}/clients/{cid}/optional-client-scopes"
URL_CLIENT_OPTIONAL_CLIENT_SCOPE = "{url}/admin/realms/{realm}/clients/{cid}/optional-client-scopes/{id}" URL_CLIENT_OPTIONAL_CLIENTSCOPE = "{url}/admin/realms/{realm}/clients/{cid}/optional-client-scopes/{id}"
URL_CLIENT_GROUP_ROLEMAPPINGS = "{url}/admin/realms/{realm}/groups/{id}/role-mappings/clients/{client}" URL_CLIENT_GROUP_ROLEMAPPINGS = "{url}/admin/realms/{realm}/groups/{id}/role-mappings/clients/{client}"
URL_CLIENT_GROUP_ROLEMAPPINGS_AVAILABLE = ( URL_CLIENT_GROUP_ROLEMAPPINGS_AVAILABLE = (
@@ -156,6 +154,18 @@ URL_AUTHZ_CUSTOM_POLICY = "{url}/admin/realms/{realm}/clients/{client_id}/authz/
URL_AUTHZ_CUSTOM_POLICIES = "{url}/admin/realms/{realm}/clients/{client_id}/authz/resource-server/policy" URL_AUTHZ_CUSTOM_POLICIES = "{url}/admin/realms/{realm}/clients/{client_id}/authz/resource-server/policy"
def normalize_keycloak_url(url: str) -> str:
"""Normalize Keycloak base URL for Admin REST API access.
Keycloak 17+ (Quarkus) exposes the API at the server root without an /auth prefix.
WildFly-based Keycloak used /auth as the context path. Trailing slashes are removed.
"""
url = url.rstrip("/")
if url.endswith("/auth"):
return url[:-5]
return url
def keycloak_argument_spec() -> dict[str, t.Any]: def keycloak_argument_spec() -> dict[str, t.Any]:
""" """
Returns argument_spec of options common to keycloak_*-modules Returns argument_spec of options common to keycloak_*-modules
@@ -205,7 +215,7 @@ def _token_request(module_params: dict[str, t.Any], payload: dict[str, t.Any]) -
'refresh_token' for type 'refresh_token'. 'refresh_token' for type 'refresh_token'.
:return: access token :return: access token
""" """
base_url = module_params["auth_keycloak_url"] base_url = normalize_keycloak_url(module_params["auth_keycloak_url"])
if not base_url.lower().startswith(("http", "https")): if not base_url.lower().startswith(("http", "https")):
raise KeycloakError(f"auth_url '{base_url}' should either start with 'http' or 'https'.") raise KeycloakError(f"auth_url '{base_url}' should either start with 'http' or 'https'.")
auth_realm = module_params.get("auth_realm") auth_realm = module_params.get("auth_realm")
@@ -393,7 +403,7 @@ class KeycloakAPI:
def __init__(self, module: AnsibleModule, connection_header: dict[str, str]) -> None: def __init__(self, module: AnsibleModule, connection_header: dict[str, str]) -> None:
self.module = module self.module = module
self.baseurl = self.module.params.get("auth_keycloak_url") self.baseurl = normalize_keycloak_url(self.module.params.get("auth_keycloak_url"))
self.validate_certs = self.module.params.get("validate_certs") self.validate_certs = self.module.params.get("validate_certs")
self.connection_timeout = self.module.params.get("connection_timeout") self.connection_timeout = self.module.params.get("connection_timeout")
self.restheaders = connection_header self.restheaders = connection_header
@@ -703,7 +713,7 @@ class KeycloakAPI:
except Exception as e: except Exception as e:
self.fail_request(e, msg=f"Could not obtain list of clients for realm {realm}: {e}") self.fail_request(e, msg=f"Could not obtain list of clients for realm {realm}: {e}")
def get_client_by_client_id(self, client_id, realm: str = "master"): def get_client_by_clientid(self, client_id, realm: str = "master"):
"""Get client representation by clientId """Get client representation by clientId
:param client_id: The clientId to be queried :param client_id: The clientId to be queried
:param realm: realm from which to obtain the client representation :param realm: realm from which to obtain the client representation
@@ -746,7 +756,7 @@ class KeycloakAPI:
:param realm: client template from this realm :param realm: client template from this realm
:return: id of client (usually a UUID) :return: id of client (usually a UUID)
""" """
result = self.get_client_by_client_id(client_id, realm) result = self.get_client_by_clientid(client_id, realm)
if isinstance(result, dict) and "id" in result: if isinstance(result, dict) and "id" in result:
return result["id"] return result["id"]
else: else:
@@ -1291,99 +1301,99 @@ class KeycloakAPI:
except Exception as e: except Exception as e:
self.fail_request(e, msg=f"Could not delete client template {id} in realm {realm}: {e}") self.fail_request(e, msg=f"Could not delete client template {id} in realm {realm}: {e}")
def get_client_scopes(self, realm: str = "master"): def get_clientscopes(self, realm: str = "master"):
"""Fetch the name and ID of all client scopes on the Keycloak server. """Fetch the name and ID of all clientscopes on the Keycloak server.
To fetch the full data of the group, make a subsequent call to To fetch the full data of the group, make a subsequent call to
get_client_scope_by_client_scope_id, passing in the ID of the group you wish to return. get_clientscope_by_clientscopeid, passing in the ID of the group you wish to return.
:param realm: Realm in which the client scope resides; default 'master'. :param realm: Realm in which the clientscope resides; default 'master'.
:return The client scopes of this realm (default "master") :return The clientscopes of this realm (default "master")
""" """
client_scopes_url = URL_CLIENT_SCOPES.format(url=self.baseurl, realm=realm) clientscopes_url = URL_CLIENTSCOPES.format(url=self.baseurl, realm=realm)
try: try:
return self._request_and_deserialize(client_scopes_url, method="GET") return self._request_and_deserialize(clientscopes_url, method="GET")
except Exception as e: except Exception as e:
self.fail_request(e, msg=f"Could not fetch list of client scopes in realm {realm}: {e}") self.fail_request(e, msg=f"Could not fetch list of clientscopes in realm {realm}: {e}")
def get_client_scope_by_client_scope_id(self, cid, realm: str = "master"): def get_clientscope_by_clientscopeid(self, cid, realm: str = "master"):
"""Fetch a keycloak client scope from the provided realm using the client scope's unique ID. """Fetch a keycloak clientscope from the provided realm using the clientscope's unique ID.
If the client scope does not exist, None is returned. If the clientscope does not exist, None is returned.
gid is a UUID provided by the Keycloak API gid is a UUID provided by the Keycloak API
:param cid: UUID of the client scope to be returned :param cid: UUID of the clientscope to be returned
:param realm: Realm in which the client scope resides; default 'master'. :param realm: Realm in which the clientscope resides; default 'master'.
""" """
client_scope_url = URL_CLIENT_SCOPE.format(url=self.baseurl, realm=realm, id=cid) clientscope_url = URL_CLIENTSCOPE.format(url=self.baseurl, realm=realm, id=cid)
try: try:
return self._request_and_deserialize(client_scope_url, method="GET") return self._request_and_deserialize(clientscope_url, method="GET")
except HTTPError as e: except HTTPError as e:
if e.code == HTTPStatus.NOT_FOUND: if e.code == HTTPStatus.NOT_FOUND:
return None return None
else: else:
self.fail_request(e, msg=f"Could not fetch client scope {cid} in realm {realm}: {e}") self.fail_request(e, msg=f"Could not fetch clientscope {cid} in realm {realm}: {e}")
except Exception as e: except Exception as e:
self.module.fail_json(msg=f"Could not client scope group {cid} in realm {realm}: {e}") self.module.fail_json(msg=f"Could not clientscope group {cid} in realm {realm}: {e}")
def get_client_scope_by_name(self, name, realm: str = "master"): def get_clientscope_by_name(self, name, realm: str = "master"):
"""Fetch a keycloak client scope within a realm based on its name. """Fetch a keycloak clientscope within a realm based on its name.
The Keycloak API does not allow filtering of the client scopes resource by name. The Keycloak API does not allow filtering of the clientscopes resource by name.
As a result, this method first retrieves the entire list of client scopes - name and ID - As a result, this method first retrieves the entire list of clientscopes - name and ID -
then performs a second query to fetch the group. then performs a second query to fetch the group.
If the client scope does not exist, None is returned. If the clientscope does not exist, None is returned.
:param name: Name of the client scope to fetch. :param name: Name of the clientscope to fetch.
:param realm: Realm in which the client scope resides; default 'master' :param realm: Realm in which the clientscope resides; default 'master'
""" """
try: try:
all_client_scopes = self.get_client_scopes(realm=realm) all_clientscopes = self.get_clientscopes(realm=realm)
for client_scope in all_client_scopes: for clientscope in all_clientscopes:
if client_scope["name"] == name: if clientscope["name"] == name:
return self.get_client_scope_by_client_scope_id(client_scope["id"], realm=realm) return self.get_clientscope_by_clientscopeid(clientscope["id"], realm=realm)
return None return None
except Exception as e: except Exception as e:
self.module.fail_json(msg=f"Could not fetch client scope {name} in realm {realm}: {e}") self.module.fail_json(msg=f"Could not fetch clientscope {name} in realm {realm}: {e}")
def create_client_scope(self, client_scope_rep, realm: str = "master"): def create_clientscope(self, clientscoperep, realm: str = "master"):
"""Create a Keycloak client scope. """Create a Keycloak clientscope.
:param client_scope_rep: a ClientScopeRepresentation of the client scope to be created. Must contain at minimum the field name. :param clientscoperep: a ClientScopeRepresentation of the clientscope to be created. Must contain at minimum the field name.
:return: HTTPResponse object on success :return: HTTPResponse object on success
""" """
client_scopes_url = URL_CLIENT_SCOPES.format(url=self.baseurl, realm=realm) clientscopes_url = URL_CLIENTSCOPES.format(url=self.baseurl, realm=realm)
try: try:
return self._request(client_scopes_url, method="POST", data=json.dumps(client_scope_rep)) return self._request(clientscopes_url, method="POST", data=json.dumps(clientscoperep))
except Exception as e: except Exception as e:
self.fail_request(e, msg=f"Could not create client scope {client_scope_rep['name']} in realm {realm}: {e}") self.fail_request(e, msg=f"Could not create clientscope {clientscoperep['name']} in realm {realm}: {e}")
def update_client_scope(self, client_scope_rep, realm: str = "master"): def update_clientscope(self, clientscoperep, realm: str = "master"):
"""Update an existing client scope. """Update an existing clientscope.
:param grouprep: A GroupRepresentation of the updated group. :param grouprep: A GroupRepresentation of the updated group.
:return HTTPResponse object on success :return HTTPResponse object on success
""" """
client_scope_url = URL_CLIENT_SCOPE.format(url=self.baseurl, realm=realm, id=client_scope_rep["id"]) clientscope_url = URL_CLIENTSCOPE.format(url=self.baseurl, realm=realm, id=clientscoperep["id"])
try: try:
return self._request(client_scope_url, method="PUT", data=json.dumps(client_scope_rep)) return self._request(clientscope_url, method="PUT", data=json.dumps(clientscoperep))
except Exception as e: except Exception as e:
self.fail_request(e, msg=f"Could not update client scope {client_scope_rep['name']} in realm {realm}: {e}") self.fail_request(e, msg=f"Could not update clientscope {clientscoperep['name']} in realm {realm}: {e}")
def delete_client_scope(self, name=None, cid=None, realm: str = "master"): def delete_clientscope(self, name=None, cid=None, realm: str = "master"):
"""Delete a client scope. One of name or cid must be provided. """Delete a clientscope. One of name or cid must be provided.
Providing the client scope ID is preferred as it avoids a second lookup to Providing the clientscope ID is preferred as it avoids a second lookup to
convert a client scope name to an ID. convert a clientscope name to an ID.
:param name: The name of the client scope. A lookup will be performed to retrieve the client scope ID. :param name: The name of the clientscope. A lookup will be performed to retrieve the clientscope ID.
:param cid: The ID of the client scope (preferred to name). :param cid: The ID of the clientscope (preferred to name).
:param realm: The realm in which this group resides, default "master". :param realm: The realm in which this group resides, default "master".
""" """
@@ -1395,9 +1405,9 @@ class KeycloakAPI:
# in the case that both are provided, prefer the ID, since it is one # in the case that both are provided, prefer the ID, since it is one
# less lookup. # less lookup.
if cid is None and name is not None: if cid is None and name is not None:
for client_scope in self.get_client_scopes(realm=realm): for clientscope in self.get_clientscopes(realm=realm):
if client_scope["name"] == name: if clientscope["name"] == name:
cid = client_scope["id"] cid = clientscope["id"]
break break
# if the group doesn't exist - no problem, nothing to delete. # if the group doesn't exist - no problem, nothing to delete.
@@ -1405,41 +1415,41 @@ class KeycloakAPI:
return None return None
# should have a good cid by here. # should have a good cid by here.
client_scope_url = URL_CLIENT_SCOPE.format(realm=realm, id=cid, url=self.baseurl) clientscope_url = URL_CLIENTSCOPE.format(realm=realm, id=cid, url=self.baseurl)
try: try:
return self._request(client_scope_url, method="DELETE") return self._request(clientscope_url, method="DELETE")
except Exception as e: except Exception as e:
self.fail_request(e, msg=f"Unable to delete client scope {cid}: {e}") self.fail_request(e, msg=f"Unable to delete clientscope {cid}: {e}")
def get_client_scope_protocolmappers(self, cid, realm: str = "master"): def get_clientscope_protocolmappers(self, cid, realm: str = "master"):
"""Fetch the name and ID of all client scopes on the Keycloak server. """Fetch the name and ID of all clientscopes on the Keycloak server.
To fetch the full data of the group, make a subsequent call to To fetch the full data of the group, make a subsequent call to
get_client_scope_by_client_scope_id, passing in the ID of the group you wish to return. get_clientscope_by_clientscopeid, passing in the ID of the group you wish to return.
:param cid: id of client scope (not name). :param cid: id of clientscope (not name).
:param realm: Realm in which the client scope resides; default 'master'. :param realm: Realm in which the clientscope resides; default 'master'.
:return The protocolmappers of this realm (default "master") :return The protocolmappers of this realm (default "master")
""" """
protocolmappers_url = URL_CLIENT_SCOPE_PROTOCOLMAPPERS.format(id=cid, url=self.baseurl, realm=realm) protocolmappers_url = URL_CLIENTSCOPE_PROTOCOLMAPPERS.format(id=cid, url=self.baseurl, realm=realm)
try: try:
return self._request_and_deserialize(protocolmappers_url, method="GET") return self._request_and_deserialize(protocolmappers_url, method="GET")
except Exception as e: except Exception as e:
self.fail_request(e, msg=f"Could not fetch list of protocolmappers in realm {realm}: {e}") self.fail_request(e, msg=f"Could not fetch list of protocolmappers in realm {realm}: {e}")
def get_client_scope_protocolmapper_by_protocolmapperid(self, pid, cid, realm: str = "master"): def get_clientscope_protocolmapper_by_protocolmapperid(self, pid, cid, realm: str = "master"):
"""Fetch a keycloak client scope from the provided realm using the client scope's unique ID. """Fetch a keycloak clientscope from the provided realm using the clientscope's unique ID.
If the client scope does not exist, None is returned. If the clientscope does not exist, None is returned.
gid is a UUID provided by the Keycloak API gid is a UUID provided by the Keycloak API
:param cid: UUID of the protocolmapper to be returned :param cid: UUID of the protocolmapper to be returned
:param cid: UUID of the client scope to be returned :param cid: UUID of the clientscope to be returned
:param realm: Realm in which the client scope resides; default 'master'. :param realm: Realm in which the clientscope resides; default 'master'.
""" """
protocolmapper_url = URL_CLIENT_SCOPE_PROTOCOLMAPPER.format(url=self.baseurl, realm=realm, id=cid, mapper_id=pid) protocolmapper_url = URL_CLIENTSCOPE_PROTOCOLMAPPER.format(url=self.baseurl, realm=realm, id=cid, mapper_id=pid)
try: try:
return self._request_and_deserialize(protocolmapper_url, method="GET") return self._request_and_deserialize(protocolmapper_url, method="GET")
@@ -1451,24 +1461,24 @@ class KeycloakAPI:
except Exception as e: except Exception as e:
self.module.fail_json(msg=f"Could not fetch protocolmapper {cid} in realm {realm}: {e}") self.module.fail_json(msg=f"Could not fetch protocolmapper {cid} in realm {realm}: {e}")
def get_client_scope_protocolmapper_by_name(self, cid, name, realm: str = "master"): def get_clientscope_protocolmapper_by_name(self, cid, name, realm: str = "master"):
"""Fetch a keycloak client scope within a realm based on its name. """Fetch a keycloak clientscope within a realm based on its name.
The Keycloak API does not allow filtering of the client scopes resource by name. The Keycloak API does not allow filtering of the clientscopes resource by name.
As a result, this method first retrieves the entire list of client scopes - name and ID - As a result, this method first retrieves the entire list of clientscopes - name and ID -
then performs a second query to fetch the group. then performs a second query to fetch the group.
If the client scope does not exist, None is returned. If the clientscope does not exist, None is returned.
:param cid: Id of the client scope (not name). :param cid: Id of the clientscope (not name).
:param name: Name of the protocolmapper to fetch. :param name: Name of the protocolmapper to fetch.
:param realm: Realm in which the client scope resides; default 'master' :param realm: Realm in which the clientscope resides; default 'master'
""" """
try: try:
all_protocolmappers = self.get_client_scope_protocolmappers(cid, realm=realm) all_protocolmappers = self.get_clientscope_protocolmappers(cid, realm=realm)
for protocolmapper in all_protocolmappers: for protocolmapper in all_protocolmappers:
if protocolmapper["name"] == name: if protocolmapper["name"] == name:
return self.get_client_scope_protocolmapper_by_protocolmapperid( return self.get_clientscope_protocolmapper_by_protocolmapperid(
protocolmapper["id"], cid, realm=realm protocolmapper["id"], cid, realm=realm
) )
@@ -1477,27 +1487,27 @@ class KeycloakAPI:
except Exception as e: except Exception as e:
self.module.fail_json(msg=f"Could not fetch protocolmapper {name} in realm {realm}: {e}") self.module.fail_json(msg=f"Could not fetch protocolmapper {name} in realm {realm}: {e}")
def create_client_scope_protocolmapper(self, cid, mapper_rep, realm: str = "master"): def create_clientscope_protocolmapper(self, cid, mapper_rep, realm: str = "master"):
"""Create a Keycloak client scope protocolmapper. """Create a Keycloak clientscope protocolmapper.
:param cid: Id of the client scope. :param cid: Id of the clientscope.
:param mapper_rep: a ProtocolMapperRepresentation of the protocolmapper to be created. Must contain at minimum the field name. :param mapper_rep: a ProtocolMapperRepresentation of the protocolmapper to be created. Must contain at minimum the field name.
:return: HTTPResponse object on success :return: HTTPResponse object on success
""" """
protocolmappers_url = URL_CLIENT_SCOPE_PROTOCOLMAPPERS.format(url=self.baseurl, id=cid, realm=realm) protocolmappers_url = URL_CLIENTSCOPE_PROTOCOLMAPPERS.format(url=self.baseurl, id=cid, realm=realm)
try: try:
return self._request(protocolmappers_url, method="POST", data=json.dumps(mapper_rep)) return self._request(protocolmappers_url, method="POST", data=json.dumps(mapper_rep))
except Exception as e: except Exception as e:
self.fail_request(e, msg=f"Could not create protocolmapper {mapper_rep['name']} in realm {realm}: {e}") self.fail_request(e, msg=f"Could not create protocolmapper {mapper_rep['name']} in realm {realm}: {e}")
def update_client_scope_protocolmappers(self, cid, mapper_rep, realm: str = "master"): def update_clientscope_protocolmappers(self, cid, mapper_rep, realm: str = "master"):
"""Update an existing client scope. """Update an existing clientscope.
:param cid: Id of the client scope. :param cid: Id of the clientscope.
:param mapper_rep: A ProtocolMapperRepresentation of the updated protocolmapper. :param mapper_rep: A ProtocolMapperRepresentation of the updated protocolmapper.
:return HTTPResponse object on success :return HTTPResponse object on success
""" """
protocolmapper_url = URL_CLIENT_SCOPE_PROTOCOLMAPPER.format( protocolmapper_url = URL_CLIENTSCOPE_PROTOCOLMAPPER.format(
url=self.baseurl, realm=realm, id=cid, mapper_id=mapper_rep["id"] url=self.baseurl, realm=realm, id=cid, mapper_id=mapper_rep["id"]
) )
@@ -1506,137 +1516,137 @@ class KeycloakAPI:
except Exception as e: except Exception as e:
self.fail_request( self.fail_request(
e, msg=f"Could not update protocolmappers for client scope {mapper_rep} in realm {realm}: {e}" e, msg=f"Could not update protocolmappers for clientscope {mapper_rep} in realm {realm}: {e}"
) )
def get_default_client_scopes(self, realm, client_id=None): def get_default_clientscopes(self, realm, client_id=None):
"""Fetch the name and ID of all client scopes on the Keycloak server. """Fetch the name and ID of all clientscopes on the Keycloak server.
To fetch the full data of the client scope, make a subsequent call to To fetch the full data of the client scope, make a subsequent call to
get_client_scope_by_client_scope_id, passing in the ID of the client scope you wish to return. get_clientscope_by_clientscopeid, passing in the ID of the client scope you wish to return.
:param realm: Realm in which the client scope resides. :param realm: Realm in which the clientscope resides.
:param client_id: The client in which the client scope resides. :param client_id: The client in which the clientscope resides.
:return The default client scopes of this realm or client :return The default clientscopes of this realm or client
""" """
url = URL_DEFAULT_CLIENT_SCOPES if client_id is None else URL_CLIENT_DEFAULT_CLIENT_SCOPES url = URL_DEFAULT_CLIENTSCOPES if client_id is None else URL_CLIENT_DEFAULT_CLIENTSCOPES
return self._get_client_scopes_of_type(realm, url, "default", client_id) return self._get_clientscopes_of_type(realm, url, "default", client_id)
def get_optional_client_scopes(self, realm, client_id=None): def get_optional_clientscopes(self, realm, client_id=None):
"""Fetch the name and ID of all client scopes on the Keycloak server. """Fetch the name and ID of all clientscopes on the Keycloak server.
To fetch the full data of the client scope, make a subsequent call to To fetch the full data of the client scope, make a subsequent call to
get_client_scope_by_client_scope_id, passing in the ID of the client scope you wish to return. get_clientscope_by_clientscopeid, passing in the ID of the client scope you wish to return.
:param realm: Realm in which the client scope resides. :param realm: Realm in which the clientscope resides.
:param client_id: The client in which the client scope resides. :param client_id: The client in which the clientscope resides.
:return The optional client scopes of this realm or client :return The optional clientscopes of this realm or client
""" """
url = URL_OPTIONAL_CLIENT_SCOPES if client_id is None else URL_CLIENT_OPTIONAL_CLIENT_SCOPES url = URL_OPTIONAL_CLIENTSCOPES if client_id is None else URL_CLIENT_OPTIONAL_CLIENTSCOPES
return self._get_client_scopes_of_type(realm, url, "optional", client_id) return self._get_clientscopes_of_type(realm, url, "optional", client_id)
def _get_client_scopes_of_type(self, realm, url_template, scope_type, client_id=None): def _get_clientscopes_of_type(self, realm, url_template, scope_type, client_id=None):
"""Fetch the name and ID of all client scopes on the Keycloak server. """Fetch the name and ID of all clientscopes on the Keycloak server.
To fetch the full data of the client scope, make a subsequent call to To fetch the full data of the client scope, make a subsequent call to
get_client_scope_by_client_scope_id, passing in the ID of the client scope you wish to return. get_clientscope_by_clientscopeid, passing in the ID of the client scope you wish to return.
:param realm: Realm in which the client scope resides. :param realm: Realm in which the clientscope resides.
:param url_template the template for the right type :param url_template the template for the right type
:param scope_type this can be either optional or default :param scope_type this can be either optional or default
:param client_id: The client in which the client scope resides. :param client_id: The client in which the clientscope resides.
:return The client scopes of the specified type of this realm :return The clientscopes of the specified type of this realm
""" """
if client_id is None: if client_id is None:
client_scopes_url = url_template.format(url=self.baseurl, realm=realm) clientscopes_url = url_template.format(url=self.baseurl, realm=realm)
try: try:
return self._request_and_deserialize(client_scopes_url, method="GET") return self._request_and_deserialize(clientscopes_url, method="GET")
except Exception as e: except Exception as e:
self.fail_request(e, msg=f"Could not fetch list of {scope_type} client scopes in realm {realm}: {e}") self.fail_request(e, msg=f"Could not fetch list of {scope_type} clientscopes in realm {realm}: {e}")
else: else:
cid = self.get_client_id(client_id=client_id, realm=realm) cid = self.get_client_id(client_id=client_id, realm=realm)
client_scopes_url = url_template.format(url=self.baseurl, realm=realm, cid=cid) clientscopes_url = url_template.format(url=self.baseurl, realm=realm, cid=cid)
try: try:
return self._request_and_deserialize(client_scopes_url, method="GET") return self._request_and_deserialize(clientscopes_url, method="GET")
except Exception as e: except Exception as e:
self.fail_request( self.fail_request(
e, e,
msg=f"Could not fetch list of {scope_type} client scopes in client {client_id}: {client_scopes_url}", msg=f"Could not fetch list of {scope_type} clientscopes in client {client_id}: {clientscopes_url}",
) )
def _decide_url_type_client_scope(self, client_id=None, scope_type="default"): def _decide_url_type_clientscope(self, client_id=None, scope_type="default"):
"""Decides which url to use. """Decides which url to use.
:param scope_type this can be either optional or default :param scope_type this can be either optional or default
:param client_id: The client in which the client scope resides. :param client_id: The client in which the clientscope resides.
""" """
if client_id is None: if client_id is None:
if scope_type == "default": if scope_type == "default":
return URL_DEFAULT_CLIENT_SCOPE return URL_DEFAULT_CLIENTSCOPE
if scope_type == "optional": if scope_type == "optional":
return URL_OPTIONAL_CLIENT_SCOPE return URL_OPTIONAL_CLIENTSCOPE
else: else:
if scope_type == "default": if scope_type == "default":
return URL_CLIENT_DEFAULT_CLIENT_SCOPE return URL_CLIENT_DEFAULT_CLIENTSCOPE
if scope_type == "optional": if scope_type == "optional":
return URL_CLIENT_OPTIONAL_CLIENT_SCOPE return URL_CLIENT_OPTIONAL_CLIENTSCOPE
def add_default_client_scope(self, id, realm: str = "master", client_id=None): def add_default_clientscope(self, id, realm: str = "master", client_id=None):
"""Add a client scope as default either on realm or client level. """Add a client scope as default either on realm or client level.
:param id: Client scope Id. :param id: Client scope Id.
:param realm: Realm in which the client scope resides. :param realm: Realm in which the clientscope resides.
:param client_id: The client in which the client scope resides. :param client_id: The client in which the clientscope resides.
""" """
self._action_type_client_scope(id, client_id, "default", realm, "add") self._action_type_clientscope(id, client_id, "default", realm, "add")
def add_optional_client_scope(self, id, realm: str = "master", client_id=None): def add_optional_clientscope(self, id, realm: str = "master", client_id=None):
"""Add a client scope as optional either on realm or client level. """Add a client scope as optional either on realm or client level.
:param id: Client scope Id. :param id: Client scope Id.
:param realm: Realm in which the client scope resides. :param realm: Realm in which the clientscope resides.
:param client_id: The client in which the client scope resides. :param client_id: The client in which the clientscope resides.
""" """
self._action_type_client_scope(id, client_id, "optional", realm, "add") self._action_type_clientscope(id, client_id, "optional", realm, "add")
def delete_default_client_scope(self, id, realm: str = "master", client_id=None): def delete_default_clientscope(self, id, realm: str = "master", client_id=None):
"""Remove a client scope as default either on realm or client level. """Remove a client scope as default either on realm or client level.
:param id: Client scope Id. :param id: Client scope Id.
:param realm: Realm in which the client scope resides. :param realm: Realm in which the clientscope resides.
:param client_id: The client in which the client scope resides. :param client_id: The client in which the clientscope resides.
""" """
self._action_type_client_scope(id, client_id, "default", realm, "delete") self._action_type_clientscope(id, client_id, "default", realm, "delete")
def delete_optional_client_scope(self, id, realm: str = "master", client_id=None): def delete_optional_clientscope(self, id, realm: str = "master", client_id=None):
"""Remove a client scope as optional either on realm or client level. """Remove a client scope as optional either on realm or client level.
:param id: Client scope Id. :param id: Client scope Id.
:param realm: Realm in which the client scope resides. :param realm: Realm in which the clientscope resides.
:param client_id: The client in which the client scope resides. :param client_id: The client in which the clientscope resides.
""" """
self._action_type_client_scope(id, client_id, "optional", realm, "delete") self._action_type_clientscope(id, client_id, "optional", realm, "delete")
def _action_type_client_scope( def _action_type_clientscope(
self, id=None, client_id=None, scope_type="default", realm: str = "master", action="add" self, id=None, client_id=None, scope_type="default", realm: str = "master", action="add"
): ):
"""Delete or add a client scope of type. """Delete or add a clientscope of type.
:param name: The name of the client scope. A lookup will be performed to retrieve the client scope ID. :param name: The name of the clientscope. A lookup will be performed to retrieve the clientscope ID.
:param client_id: The ID of the client scope (preferred to name). :param client_id: The ID of the clientscope (preferred to name).
:param scope_type 'default' or 'optional' :param scope_type 'default' or 'optional'
:param realm: The realm in which this group resides, default "master". :param realm: The realm in which this group resides, default "master".
""" """
cid = None if client_id is None else self.get_client_id(client_id=client_id, realm=realm) cid = None if client_id is None else self.get_client_id(client_id=client_id, realm=realm)
# should have a good cid by here. # should have a good cid by here.
client_scope_type_url = self._decide_url_type_client_scope(client_id, scope_type).format( clientscope_type_url = self._decide_url_type_clientscope(client_id, scope_type).format(
realm=realm, id=id, cid=cid, url=self.baseurl realm=realm, id=id, cid=cid, url=self.baseurl
) )
try: try:
method = "PUT" if action == "add" else "DELETE" method = "PUT" if action == "add" else "DELETE"
return self._request(client_scope_type_url, method=method) return self._request(clientscope_type_url, method=method)
except Exception as e: except Exception as e:
place = "realm" if client_id is None else f"client {client_id}" place = "realm" if client_id is None else f"client {client_id}"
self.fail_request(e, msg=f"Unable to {action} {scope_type} client scope {id} @ {place} : {e}") self.fail_request(e, msg=f"Unable to {action} {scope_type} clientscope {id} @ {place} : {e}")
def create_clientsecret(self, id, realm: str = "master"): def create_clientsecret(self, id, realm: str = "master"):
"""Generate a new client secret by id """Generate a new client secret by id
@@ -2022,7 +2032,7 @@ class KeycloakAPI:
composite_url = "" composite_url = ""
try: try:
if clientid is not None: if clientid is not None:
client = self.get_client_by_client_id(client_id=clientid, realm=realm) client = self.get_client_by_clientid(client_id=clientid, realm=realm)
cid = client["id"] cid = client["id"]
composite_url = URL_CLIENT_ROLE_COMPOSITES.format( composite_url = URL_CLIENT_ROLE_COMPOSITES.format(
url=self.baseurl, realm=realm, id=cid, name=quote(rolerep["name"], safe="") url=self.baseurl, realm=realm, id=cid, name=quote(rolerep["name"], safe="")
@@ -2040,7 +2050,7 @@ class KeycloakAPI:
composite_url = "" composite_url = ""
try: try:
if clientid is not None: if clientid is not None:
client = self.get_client_by_client_id(client_id=clientid, realm=realm) client = self.get_client_by_clientid(client_id=clientid, realm=realm)
cid = client["id"] cid = client["id"]
composite_url = URL_CLIENT_ROLE_COMPOSITES.format( composite_url = URL_CLIENT_ROLE_COMPOSITES.format(
url=self.baseurl, realm=realm, id=cid, name=quote(rolerep["name"], safe="") url=self.baseurl, realm=realm, id=cid, name=quote(rolerep["name"], safe="")
@@ -2059,7 +2069,7 @@ class KeycloakAPI:
composite_url = "" composite_url = ""
try: try:
if clientid is not None: if clientid is not None:
client = self.get_client_by_client_id(client_id=clientid, realm=realm) client = self.get_client_by_clientid(client_id=clientid, realm=realm)
cid = client["id"] cid = client["id"]
composite_url = URL_CLIENT_ROLE_COMPOSITES.format( composite_url = URL_CLIENT_ROLE_COMPOSITES.format(
url=self.baseurl, realm=realm, id=cid, name=quote(rolerep["name"], safe="") url=self.baseurl, realm=realm, id=cid, name=quote(rolerep["name"], safe="")
@@ -3261,192 +3271,192 @@ class KeycloakAPI:
except Exception: except Exception:
return False return False
def get_all_client_scope_scope_mappings(self, client_scope_id, realm: str = "master"): def get_all_clientscope_scope_mappings(self, clientscope_id, realm: str = "master"):
"""Fetch all (realm and client) roles (scope-mappings) associated with the client scope for a specific client scope on the Keycloak server. """Fetch all (realm and client) roles (scope-mappings) associated with the clientscope for a specific clientscope on the Keycloak server.
:param client_scope_id: ID of the client scope from which to obtain the associated roles. :param clientscope_id: ID of the clientscope from which to obtain the associated roles.
:param realm: Realm from which to obtain the scope. :param realm: Realm from which to obtain the scope.
:return: The client scope scope-mappings. :return: The clientscope scope-mappings.
""" """
client_role_scope_url = URL_CLIENT_SCOPE_SCOPE_MAPPINGS.format(url=self.baseurl, realm=realm, id=client_scope_id) client_role_scope_url = URL_CLIENTSCOPE_SCOPE_MAPPINGS.format(url=self.baseurl, realm=realm, id=clientscope_id)
try: try:
return self._request_and_deserialize(client_role_scope_url, method="GET") return self._request_and_deserialize(client_role_scope_url, method="GET")
except Exception as e: except Exception as e:
self.fail_request(e, msg=f"Could not fetch roles for client scope {client_scope_id} in realm {realm}: {e}") self.fail_request(e, msg=f"Could not fetch roles for client-scope {clientscope_id} in realm {realm}: {e}")
def get_client_scope_scope_mappings_realm(self, client_scope_id, realm: str = "master"): def get_clientscope_scope_mappings_realm(self, clientscope_id, realm: str = "master"):
"""Fetch the realm roles (scope-mappings) associated with the client scope for a specific client scope on the Keycloak server. """Fetch the realm roles (scope-mappings) associated with the clientscope for a specific clientscope on the Keycloak server.
:param client_scope_id: ID of the client scope from which to obtain the associated roles. :param clientscope_id: ID of the clientscope from which to obtain the associated roles.
:param realm: Realm from which to obtain the scope. :param realm: Realm from which to obtain the scope.
:return: The client scope realm scope-mappings. :return: The clientscope realm scope-mappings.
""" """
client_role_scope_url = URL_CLIENT_SCOPE_SCOPE_MAPPINGS_REALM.format( client_role_scope_url = URL_CLIENTSCOPE_SCOPE_MAPPINGS_REALM.format(
url=self.baseurl, realm=realm, id=client_scope_id url=self.baseurl, realm=realm, id=clientscope_id
) )
try: try:
return self._request_and_deserialize(client_role_scope_url, method="GET") return self._request_and_deserialize(client_role_scope_url, method="GET")
except Exception as e: except Exception as e:
self.fail_request( self.fail_request(
e, msg=f"Could not fetch realm roles for client scope {client_scope_id} in realm {realm}: {e}" e, msg=f"Could not fetch realm roles for client-scope {clientscope_id} in realm {realm}: {e}"
) )
def get_client_scope_scope_mappings_client(self, client_scope_id, client_id, realm: str = "master"): def get_clientscope_scope_mappings_client(self, clientscope_id, client_id, realm: str = "master"):
"""Fetch the client roles (scope-mappings) associated with the client scope for a specific client scope and client on the Keycloak server. """Fetch the client roles (scope-mappings) associated with the clientscope for a specific clientscope and client on the Keycloak server.
:param client_scope_id: ID of the client scope from which to obtain the associated roles. :param clientscope_id: ID of the clientscope from which to obtain the associated roles.
:param clientid: ID of the client from which to obtain the associated roles. :param clientid: ID of the client from which to obtain the associated roles.
:param realm: Realm from which to obtain the scope. :param realm: Realm from which to obtain the scope.
:return: The client scope client scope-mappings. :return: The clientscope client scope-mappings.
""" """
client_role_scope_url = URL_CLIENT_SCOPE_SCOPE_MAPPINGS_CLIENT.format( client_role_scope_url = URL_CLIENTSCOPE_SCOPE_MAPPINGS_CLIENT.format(
url=self.baseurl, realm=realm, id=client_scope_id, client=client_id url=self.baseurl, realm=realm, id=clientscope_id, client=client_id
) )
try: try:
return self._request_and_deserialize(client_role_scope_url, method="GET") return self._request_and_deserialize(client_role_scope_url, method="GET")
except Exception as e: except Exception as e:
self.fail_request( self.fail_request(
e, e,
msg=f"Could not fetch client roles from client {client_id} for client scope {client_scope_id} in realm {realm}: {e}", msg=f"Could not fetch client roles from client {client_id} for client-scope {clientscope_id} in realm {realm}: {e}",
) )
def get_client_role_scope_from_client(self, target_client_id, role_owner_client_id, realm: str = "master"): def get_client_role_scope_from_client(self, clientid, clientscopeid, realm: str = "master"):
"""Fetch the roles associated with the client's scope for a specific client on the Keycloak server. """Fetch the roles associated with the client's scope for a specific client on the Keycloak server.
:param target_client_id: ID of the client from which to obtain the associated roles. :param clientid: ID of the client from which to obtain the associated roles.
:param role_owner_client_id: ID of the client who owns the roles. :param clientscopeid: ID of the client who owns the roles.
:param realm: Realm from which to obtain the scope. :param realm: Realm from which to obtain the scope.
:return: The client scope of roles from specified client. :return: The client scope of roles from specified client.
""" """
client_role_scope_url = URL_CLIENT_ROLE_SCOPE_CLIENTS.format( client_role_scope_url = URL_CLIENT_ROLE_SCOPE_CLIENTS.format(
url=self.baseurl, realm=realm, id=target_client_id, scopeid=role_owner_client_id url=self.baseurl, realm=realm, id=clientid, scopeid=clientscopeid
) )
try: try:
return self._request_and_deserialize(client_role_scope_url, method="GET") return self._request_and_deserialize(client_role_scope_url, method="GET")
except Exception as e: except Exception as e:
self.fail_request(e, msg=f"Could not fetch roles scope for client {target_client_id} in realm {realm}: {e}") self.fail_request(e, msg=f"Could not fetch roles scope for client {clientid} in realm {realm}: {e}")
def update_client_role_scope_from_client(self, roles, target_client_id, role_owner_client_id, realm: str = "master"): def update_client_role_scope_from_client(self, payload, clientid, clientscopeid, realm: str = "master"):
"""Update and fetch the roles associated with the client's scope on the Keycloak server. """Update and fetch the roles associated with the client's scope on the Keycloak server.
:param roles: List of roles to be added to the scope. :param payload: List of roles to be added to the scope.
:param target_client_id: ID of the client to update scope. :param clientid: ID of the client to update scope.
:param role_owner_client_id: ID of the client who owns the roles. :param clientscopeid: ID of the client who owns the roles.
:param realm: Realm from which to obtain the clients. :param realm: Realm from which to obtain the clients.
:return: The client scope of roles from specified client. :return: The client scope of roles from specified client.
""" """
client_role_scope_url = URL_CLIENT_ROLE_SCOPE_CLIENTS.format( client_role_scope_url = URL_CLIENT_ROLE_SCOPE_CLIENTS.format(
url=self.baseurl, realm=realm, id=target_client_id, scopeid=role_owner_client_id url=self.baseurl, realm=realm, id=clientid, scopeid=clientscopeid
) )
try: try:
self._request(client_role_scope_url, method="POST", data=json.dumps(roles)) self._request(client_role_scope_url, method="POST", data=json.dumps(payload))
except Exception as e: except Exception as e:
self.fail_request(e, msg=f"Could not update roles scope for client {target_client_id} in realm {realm}: {e}") self.fail_request(e, msg=f"Could not update roles scope for client {clientid} in realm {realm}: {e}")
return self.get_client_role_scope_from_client(target_client_id, role_owner_client_id, realm) return self.get_client_role_scope_from_client(clientid, clientscopeid, realm)
def delete_client_role_scope_from_client(self, roles, target_client_id, role_owner_client_id, realm: str = "master"): def delete_client_role_scope_from_client(self, payload, clientid, clientscopeid, realm: str = "master"):
"""Delete the roles from the client's scope on the Keycloak server. """Delete the roles contains in the payload from the client's scope on the Keycloak server.
:param roles: List of roles to be deleted. :param payload: List of roles to be deleted.
:param target_client_id: ID of the client to delete roles from scope. :param clientid: ID of the client to delete roles from scope.
:param role_owner_client_id: ID of the client who owns the roles. :param clientscopeid: ID of the client who owns the roles.
:param realm: Realm from which to obtain the clients. :param realm: Realm from which to obtain the clients.
:return: The client scope of roles from specified client. :return: The client scope of roles from specified client.
""" """
client_role_scope_url = URL_CLIENT_ROLE_SCOPE_CLIENTS.format( client_role_scope_url = URL_CLIENT_ROLE_SCOPE_CLIENTS.format(
url=self.baseurl, realm=realm, id=target_client_id, scopeid=role_owner_client_id url=self.baseurl, realm=realm, id=clientid, scopeid=clientscopeid
) )
try: try:
self._request(client_role_scope_url, method="DELETE", data=json.dumps(roles)) self._request(client_role_scope_url, method="DELETE", data=json.dumps(payload))
except Exception as e: except Exception as e:
self.fail_request(e, msg=f"Could not delete roles from scope for client {target_client_id} in realm {realm}: {e}") self.fail_request(e, msg=f"Could not delete roles scope for client {clientid} in realm {realm}: {e}")
return self.get_client_role_scope_from_client(target_client_id, role_owner_client_id, realm) return self.get_client_role_scope_from_client(clientid, clientscopeid, realm)
def update_client_scope_scope_mappings_client( def update_clientscope_scope_mappings_client(
self, roles: list[dict], client_scope_id: str, role_owner_client_id: str, realm: str = "master" self, payload: list[dict], clientscope_id: str, client_id: str, realm: str = "master"
): ):
"""Update and fetch the client roles (scope-mappings) associated with the client scope on the Keycloak server. """Update and fetch the client roles (scope-mappings) associated with the clientscope on the Keycloak server.
:param roles: List of client roles to be added to the scope. :param payload: List of client roles to be added to the scope.
:param client_scope_id: ID of the client scope to update scope-mappings. :param clientscope_id: ID of the clientscope to update scope-mappings.
:param role_owner_client_id: ID of the client from which to obtain the associated roles. :param clientid: ID of the client from which to obtain the associated roles.
:param realm: Realm from which to obtain the client. :param realm: Realm from which to obtain the client.
:return: The client scope client scope-mappings. :return: The clientscope client scope-mappings.
""" """
client_role_scope_url = URL_CLIENT_SCOPE_SCOPE_MAPPINGS_CLIENT.format( client_role_scope_url = URL_CLIENTSCOPE_SCOPE_MAPPINGS_CLIENT.format(
url=self.baseurl, realm=realm, id=client_scope_id, client=role_owner_client_id url=self.baseurl, realm=realm, id=clientscope_id, client=client_id
) )
try: try:
self._request(client_role_scope_url, method="POST", data=json.dumps(roles)) self._request(client_role_scope_url, method="POST", data=json.dumps(payload))
except Exception as e: except Exception as e:
self.fail_request( self.fail_request(
e, e,
msg=f"Could not update scope mappings for client scope {role_owner_client_id}.{client_scope_id} in realm {realm}: {e}", msg=f"Could not update scope mappings for client-scope {client_id}.{clientscope_id} in realm {realm}: {e}",
) )
return self.get_client_scope_scope_mappings_client(client_scope_id, role_owner_client_id, realm) return self.get_clientscope_scope_mappings_client(clientscope_id, client_id, realm)
def update_client_scope_scope_mappings_realm(self, roles: list[dict], client_scope_id: str, realm: str = "master"): def update_clientscope_scope_mappings_realm(self, payload: list[dict], clientscope_id: str, realm: str = "master"):
"""Update and fetch the realm roles (scope-mappings) associated with the client scope on the Keycloak server. """Update and fetch the realm roles (scope-mappings) associated with the clientscope on the Keycloak server.
:param roles: List of realm roles to be added to the scope. :param payload: List of realm roles to be added to the scope.
:param client_scope_id: ID of the client scope to update scope-mappings. :param clientscope_id: ID of the clientscope to update scope-mappings.
:param realm: Realm from which to obtain the roles. :param realm: Realm from which to obtain the roles.
:return: The client scope realm scope-mappings. :return: The clientscope realm scope-mappings.
""" """
client_role_scope_url = URL_CLIENT_SCOPE_SCOPE_MAPPINGS_REALM.format( client_role_scope_url = URL_CLIENTSCOPE_SCOPE_MAPPINGS_REALM.format(
url=self.baseurl, realm=realm, id=client_scope_id url=self.baseurl, realm=realm, id=clientscope_id
) )
try: try:
self._request(client_role_scope_url, method="POST", data=json.dumps(roles)) self._request(client_role_scope_url, method="POST", data=json.dumps(payload))
except Exception as e: except Exception as e:
self.fail_request( self.fail_request(
e, msg=f"Could not update scope mappings for client scope {client_scope_id} in realm {realm}: {e}" e, msg=f"Could not update scope mappings for client-scope {clientscope_id} in realm {realm}: {e}"
) )
return self.get_client_scope_scope_mappings_realm(client_scope_id, realm) return self.get_clientscope_scope_mappings_realm(clientscope_id, realm)
def delete_client_scope_scope_mappings_client( def delete_clientscope_scope_mappings_client(
self, roles: list[dict], client_scope_id: str, role_owner_client_id: str, realm: str = "master" self, payload: list[dict], clientscope_id: str, client_id: str, realm: str = "master"
): ):
"""Delete the client roles (scope_mappings) from the client scope on the Keycloak server. """Delete the client roles (scope_mappings) contained in the payload from the clientscope on the Keycloak server.
:param roles: List of roles to be deleted. :param payload: List of roles to be deleted.
:param client_scope_id: ID of the client scope to delete roles from scope-mappings. :param clientscope_id: ID of the clientscope to delete roles from scope-mappings.
:param role_owner_client_id: ID of the client who owns the roles. :param clientid: ID of the client who owns the roles.
:param realm: Realm from which to obtain the client. :param realm: Realm from which to obtain the client.
:return: The client scope client scope-mappings. :return: The clientscope client scope-mappings.
""" """
client_role_scope_url = URL_CLIENT_SCOPE_SCOPE_MAPPINGS_CLIENT.format( client_role_scope_url = URL_CLIENTSCOPE_SCOPE_MAPPINGS_CLIENT.format(
url=self.baseurl, realm=realm, id=client_scope_id, client=role_owner_client_id url=self.baseurl, realm=realm, id=clientscope_id, client=client_id
) )
try: try:
self._request(client_role_scope_url, method="DELETE", data=json.dumps(roles)) self._request(client_role_scope_url, method="DELETE", data=json.dumps(payload))
except Exception as e: except Exception as e:
self.fail_request( self.fail_request(
e, e,
msg=f"Could not delete scope mappings for client scope {role_owner_client_id}.{client_scope_id} in realm {realm}: {e}", msg=f"Could not delete scope mappings for client-scope {client_id}.{clientscope_id} in realm {realm}: {e}",
) )
return self.get_client_scope_scope_mappings_client(client_scope_id, role_owner_client_id, realm) return self.get_clientscope_scope_mappings_client(clientscope_id, client_id, realm)
def delete_client_scope_scope_mappings_realm(self, roles: list[dict], client_scope_id: str, realm: str = "master"): def delete_clientscope_scope_mappings_realm(self, payload: list[dict], clientscope_id: str, realm: str = "master"):
"""Delete the realm roles (scope_mappings) contained in the roles from the client scope on the Keycloak server. """Delete the realm roles (scope_mappings) contained in the payload from the clientscope on the Keycloak server.
:param roles: List of roles to be deleted. :param payload: List of roles to be deleted.
:param client_scope_id: ID of the client scope to delete roles from scope-mappings. :param clientscope_id: ID of the clientscope to delete roles from scope-mappings.
:param realm: Realm from which to obtain the roles. :param realm: Realm from which to obtain the roles.
:return: The client scope realm scope-mappings. :return: The clientscope realm scope-mappings.
""" """
client_role_scope_url = URL_CLIENT_SCOPE_SCOPE_MAPPINGS_REALM.format( client_role_scope_url = URL_CLIENTSCOPE_SCOPE_MAPPINGS_REALM.format(
url=self.baseurl, realm=realm, id=client_scope_id url=self.baseurl, realm=realm, id=clientscope_id
) )
try: try:
self._request(client_role_scope_url, method="DELETE", data=json.dumps(roles)) self._request(client_role_scope_url, method="DELETE", data=json.dumps(payload))
except Exception as e: except Exception as e:
self.fail_request( self.fail_request(
e, msg=f"Could not delete scope mappings for client scope {client_scope_id} in realm {realm}: {e}" e, msg=f"Could not delete scope mappings for client-scope {clientscope_id} in realm {realm}: {e}"
) )
return self.get_client_scope_scope_mappings_realm(client_scope_id, realm) return self.get_clientscope_scope_mappings_realm(clientscope_id, realm)
def get_client_role_scope_from_realm(self, clientid, realm: str = "master"): def get_client_role_scope_from_realm(self, clientid, realm: str = "master"):
"""Fetch the realm roles from the client's scope on the Keycloak server. """Fetch the realm roles from the client's scope on the Keycloak server.
@@ -3460,32 +3470,32 @@ class KeycloakAPI:
except Exception as e: except Exception as e:
self.fail_request(e, msg=f"Could not fetch roles scope for client {clientid} in realm {realm}: {e}") self.fail_request(e, msg=f"Could not fetch roles scope for client {clientid} in realm {realm}: {e}")
def update_client_role_scope_from_realm(self, roles, clientid, realm: str = "master"): def update_client_role_scope_from_realm(self, payload, clientid, realm: str = "master"):
"""Update and fetch the realm roles from the client's scope on the Keycloak server. """Update and fetch the realm roles from the client's scope on the Keycloak server.
:param roles: List of realm roles to add. :param payload: List of realm roles to add.
:param clientid: ID of the client to update scope. :param clientid: ID of the client to update scope.
:param realm: Realm from which to obtain the clients. :param realm: Realm from which to obtain the clients.
:return: The client realm roles scope. :return: The client realm roles scope.
""" """
client_role_scope_url = URL_CLIENT_ROLE_SCOPE_REALM.format(url=self.baseurl, realm=realm, id=clientid) client_role_scope_url = URL_CLIENT_ROLE_SCOPE_REALM.format(url=self.baseurl, realm=realm, id=clientid)
try: try:
self._request(client_role_scope_url, method="POST", data=json.dumps(roles)) self._request(client_role_scope_url, method="POST", data=json.dumps(payload))
except Exception as e: except Exception as e:
self.fail_request(e, msg=f"Could not update roles scope for client {clientid} in realm {realm}: {e}") self.fail_request(e, msg=f"Could not update roles scope for client {clientid} in realm {realm}: {e}")
return self.get_client_role_scope_from_realm(clientid, realm) return self.get_client_role_scope_from_realm(clientid, realm)
def delete_client_role_scope_from_realm(self, roles, clientid, realm: str = "master"): def delete_client_role_scope_from_realm(self, payload, clientid, realm: str = "master"):
"""Delete the realm roles from the client's scope on the Keycloak server. """Delete the realm roles contains in the payload from the client's scope on the Keycloak server.
:param roles: List of realm roles to delete. :param payload: List of realm roles to delete.
:param clientid: ID of the client to delete roles from scope. :param clientid: ID of the client to delete roles from scope.
:param realm: Realm from which to obtain the clients. :param realm: Realm from which to obtain the clients.
:return: The client realm roles scope. :return: The client realm roles scope.
""" """
client_role_scope_url = URL_CLIENT_ROLE_SCOPE_REALM.format(url=self.baseurl, realm=realm, id=clientid) client_role_scope_url = URL_CLIENT_ROLE_SCOPE_REALM.format(url=self.baseurl, realm=realm, id=clientid)
try: try:
self._request(client_role_scope_url, method="DELETE", data=json.dumps(roles)) self._request(client_role_scope_url, method="DELETE", data=json.dumps(payload))
except Exception as e: except Exception as e:
self.fail_request(e, msg=f"Could not delete roles scope for client {clientid} in realm {realm}: {e}") self.fail_request(e, msg=f"Could not delete roles scope for client {clientid} in realm {realm}: {e}")

View File

@@ -2,9 +2,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
import typing as t import typing as t
@@ -68,7 +66,7 @@ def keycloak_clientsecret_module_resolve_params(module: AnsibleModule, kc: Keycl
# less lookup. # less lookup.
if id is None: if id is None:
# Due to the required_one_of spec, client_id is guaranteed to not be None # Due to the required_one_of spec, client_id is guaranteed to not be None
client = kc.get_client_by_client_id(client_id, realm=realm) client = kc.get_client_by_clientid(client_id, realm=realm)
if client is None: if client is None:
module.fail_json(msg=f"Client does not exist {client_id}") module.fail_json(msg=f"Client does not exist {client_id}")

View File

@@ -2,9 +2,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_authentication module: keycloak_authentication

View File

@@ -5,8 +5,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function) from __future__ import absolute_import, division, print_function
from __future__ import annotations
__metaclass__ = type __metaclass__ = type
DOCUMENTATION = ''' DOCUMENTATION = '''

View File

@@ -4,9 +4,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or
# https://www.gnu.org/licenses/gpl-3.0.txt) # https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_authentication_required_actions module: keycloak_authentication_required_actions

View File

@@ -3,9 +3,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_authentication_v2 module: keycloak_authentication_v2

View File

@@ -4,9 +4,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or
# https://www.gnu.org/licenses/gpl-3.0.txt) # https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_authz_authorization_scope module: keycloak_authz_authorization_scope

View File

@@ -4,9 +4,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or
# https://www.gnu.org/licenses/gpl-3.0.txt) # https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_authz_custom_policy module: keycloak_authz_custom_policy

View File

@@ -4,9 +4,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or
# https://www.gnu.org/licenses/gpl-3.0.txt) # https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_authz_permission module: keycloak_authz_permission

View File

@@ -4,9 +4,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or
# https://www.gnu.org/licenses/gpl-3.0.txt) # https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_authz_permission_info module: keycloak_authz_permission_info

View File

@@ -3,9 +3,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_client module: keycloak_client
@@ -1102,11 +1100,11 @@ def add_default_client_scopes(desired_client, before_client, realm, kc):
missing_scopes = [item for item in desired_default_scope if item not in before_client["defaultClientScopes"]] missing_scopes = [item for item in desired_default_scope if item not in before_client["defaultClientScopes"]]
if not missing_scopes: if not missing_scopes:
return return
client_scopes = kc.get_client_scopes(realm) client_scopes = kc.get_clientscopes(realm)
for name in missing_scopes: for name in missing_scopes:
scope = find_match(client_scopes, "name", name) scope = find_match(client_scopes, "name", name)
if scope: if scope:
kc.add_default_client_scope(scope["id"], realm, desired_client["clientId"]) kc.add_default_clientscope(scope["id"], realm, desired_client["clientId"])
def add_optional_client_scopes(desired_client, before_client, realm, kc): def add_optional_client_scopes(desired_client, before_client, realm, kc):
@@ -1141,11 +1139,11 @@ def add_optional_client_scopes(desired_client, before_client, realm, kc):
missing_scopes = [item for item in desired_optional_scope if item not in before_client["optionalClientScopes"]] missing_scopes = [item for item in desired_optional_scope if item not in before_client["optionalClientScopes"]]
if not missing_scopes: if not missing_scopes:
return return
client_scopes = kc.get_client_scopes(realm) client_scopes = kc.get_clientscopes(realm)
for name in missing_scopes: for name in missing_scopes:
scope = find_match(client_scopes, "name", name) scope = find_match(client_scopes, "name", name)
if scope: if scope:
kc.add_optional_client_scope(scope["id"], realm, desired_client["clientId"]) kc.add_optional_clientscope(scope["id"], realm, desired_client["clientId"])
def remove_default_client_scopes(desired_client, before_client, realm, kc): def remove_default_client_scopes(desired_client, before_client, realm, kc):
@@ -1180,11 +1178,11 @@ def remove_default_client_scopes(desired_client, before_client, realm, kc):
missing_scopes = [item for item in before_default_scope if item not in desired_client["defaultClientScopes"]] missing_scopes = [item for item in before_default_scope if item not in desired_client["defaultClientScopes"]]
if not missing_scopes: if not missing_scopes:
return return
client_scopes = kc.get_default_client_scopes(realm, desired_client["clientId"]) client_scopes = kc.get_default_clientscopes(realm, desired_client["clientId"])
for name in missing_scopes: for name in missing_scopes:
scope = find_match(client_scopes, "name", name) scope = find_match(client_scopes, "name", name)
if scope: if scope:
kc.delete_default_client_scope(scope["id"], realm, desired_client["clientId"]) kc.delete_default_clientscope(scope["id"], realm, desired_client["clientId"])
def remove_optional_client_scopes(desired_client, before_client, realm, kc): def remove_optional_client_scopes(desired_client, before_client, realm, kc):
@@ -1219,11 +1217,11 @@ def remove_optional_client_scopes(desired_client, before_client, realm, kc):
missing_scopes = [item for item in before_optional_scope if item not in desired_client["optionalClientScopes"]] missing_scopes = [item for item in before_optional_scope if item not in desired_client["optionalClientScopes"]]
if not missing_scopes: if not missing_scopes:
return return
client_scopes = kc.get_optional_client_scopes(realm, desired_client["clientId"]) client_scopes = kc.get_optional_clientscopes(realm, desired_client["clientId"])
for name in missing_scopes: for name in missing_scopes:
scope = find_match(client_scopes, "name", name) scope = find_match(client_scopes, "name", name)
if scope: if scope:
kc.delete_optional_client_scope(scope["id"], realm, desired_client["clientId"]) kc.delete_optional_clientscope(scope["id"], realm, desired_client["clientId"])
def main(): def main():
@@ -1348,7 +1346,7 @@ def main():
# See if it already exists in Keycloak # See if it already exists in Keycloak
if cid is None: if cid is None:
before_client = kc.get_client_by_client_id(module.params.get("client_id"), realm=realm) before_client = kc.get_client_by_clientid(module.params.get("client_id"), realm=realm)
if before_client is not None: if before_client is not None:
cid = before_client["id"] cid = before_client["id"]
else: else:
@@ -1442,7 +1440,7 @@ def main():
# create it # create it
kc.create_client(desired_client, realm=realm) kc.create_client(desired_client, realm=realm)
after_client = kc.get_client_by_client_id(desired_client["clientId"], realm=realm) after_client = kc.get_client_by_clientid(desired_client["clientId"], realm=realm)
result["end_state"] = sanitize_cr(after_client) result["end_state"] = sanitize_cr(after_client)

View File

@@ -3,9 +3,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_client_rolemapping module: keycloak_client_rolemapping

View File

@@ -3,9 +3,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_client_rolescope module: keycloak_client_rolescope
@@ -21,7 +19,7 @@ description:
to the REST API using OpenID Connect; the user connecting and the client being used must have the requisite access rights. to the REST API using OpenID Connect; the user connecting and the client being used must have the requisite access rights.
In a default Keycloak installation, admin-cli and an admin user would work, as would a separate client definition with In a default Keycloak installation, admin-cli and an admin user would work, as would a separate client definition with
the scope tailored to your needs and a user having the expected roles. the scope tailored to your needs and a user having the expected roles.
- Client O(target_client_id) must have O(middleware_automation.keycloak.keycloak_client#module:full_scope_allowed) set to V(false). - Client O(client_id) must have O(middleware_automation.keycloak.keycloak_client#module:full_scope_allowed) set to V(false).
- Attributes are multi-valued in the Keycloak API. All attributes are lists of individual values and are returned that way - Attributes are multi-valued in the Keycloak API. All attributes are lists of individual values and are returned that way
by this module. You may pass single values for attributes when calling the module, and this is translated into a list by this module. You may pass single values for attributes when calling the module, and this is translated into a list
suitable for the API. suitable for the API.
@@ -52,12 +50,12 @@ options:
- The Keycloak realm under which clients resides. - The Keycloak realm under which clients resides.
default: 'master' default: 'master'
target_client_id: client_id:
type: str type: str
required: true required: true
description: description:
- Roles provided in O(role_names) while be added to this client scope. - Roles provided in O(role_names) while be added to this client scope.
role_owner_client_id: client_scope_id:
type: str type: str
description: description:
- If the O(role_names) are client role, the client ID under which it resides. - If the O(role_names) are client role, the client ID under which it resides.
@@ -68,8 +66,8 @@ options:
elements: str elements: str
description: description:
- Names of roles to manipulate. - Names of roles to manipulate.
- If O(role_owner_client_id) is present, all roles must be under this client. - If O(client_scope_id) is present, all roles must be under this client.
- If O(role_owner_client_id) is absent, all roles must be under the realm. - If O(client_scope_id) is absent, all roles must be under the realm.
extends_documentation_fragment: extends_documentation_fragment:
- middleware_automation.keycloak.keycloak - middleware_automation.keycloak.keycloak
- middleware_automation.keycloak.actiongroup_keycloak - middleware_automation.keycloak.actiongroup_keycloak
@@ -87,8 +85,8 @@ EXAMPLES = r"""
auth_username: USERNAME auth_username: USERNAME
auth_password: PASSWORD auth_password: PASSWORD
realm: MyCustomRealm realm: MyCustomRealm
target_client_id: frontend-client-public client_id: frontend-client-public
role_owner_client_id: backend-client-private client_scope_id: backend-client-private
role_names: role_names:
- backend-role-admin - backend-role-admin
- backend-role-user - backend-role-user
@@ -100,8 +98,8 @@ EXAMPLES = r"""
auth_username: USERNAME auth_username: USERNAME
auth_password: PASSWORD auth_password: PASSWORD
realm: MyCustomRealm realm: MyCustomRealm
target_client_id: frontend-client-public client_id: frontend-client-public
role_owner_client_id: backend-client-private client_scope_id: backend-client-private
role_names: role_names:
- backend-role-admin - backend-role-admin
state: absent state: absent
@@ -113,7 +111,7 @@ EXAMPLES = r"""
auth_username: USERNAME auth_username: USERNAME
auth_password: PASSWORD auth_password: PASSWORD
realm: MyCustomRealm realm: MyCustomRealm
target_client_id: frontend-client-public client_id: frontend-client-public
role_names: role_names:
- realm-role-admin - realm-role-admin
- realm-role-user - realm-role-user
@@ -169,8 +167,8 @@ def main():
argument_spec = keycloak_argument_spec() argument_spec = keycloak_argument_spec()
meta_args = dict( meta_args = dict(
target_client_id=dict(type="str", required=True), client_id=dict(type="str", required=True),
role_owner_client_id=dict(type="str"), client_scope_id=dict(type="str"),
realm=dict(type="str", default="master"), realm=dict(type="str", default="master"),
role_names=dict(type="list", elements="str", required=True), role_names=dict(type="list", elements="str", required=True),
state=dict(type="str", default="present", choices=["present", "absent"]), state=dict(type="str", default="present", choices=["present", "absent"]),
@@ -191,8 +189,8 @@ def main():
kc = KeycloakAPI(module, connection_header) kc = KeycloakAPI(module, connection_header)
realm = module.params.get("realm") realm = module.params.get("realm")
target_client_id = module.params.get("target_client_id") clientid = module.params.get("client_id")
role_owner_client_id = module.params.get("role_owner_client_id") client_scope_id = module.params.get("client_scope_id")
role_names = module.params.get("role_names") role_names = module.params.get("role_names")
state = module.params.get("state") state = module.params.get("state")
@@ -200,23 +198,23 @@ def main():
if not objRealm: if not objRealm:
module.fail_json(msg=f"Failed to retrive realm '{realm}'") module.fail_json(msg=f"Failed to retrive realm '{realm}'")
objClient = kc.get_client_by_client_id(target_client_id, realm) objClient = kc.get_client_by_clientid(clientid, realm)
if not objClient: if not objClient:
module.fail_json(msg=f"Failed to retrive client '{realm}.{target_client_id}'") module.fail_json(msg=f"Failed to retrive client '{realm}.{clientid}'")
if objClient["fullScopeAllowed"] and state == "present": if objClient["fullScopeAllowed"] and state == "present":
module.fail_json(msg=f"FullScopeAllowed is active for Client '{realm}.{target_client_id}'") module.fail_json(msg=f"FullScopeAllowed is active for Client '{realm}.{clientid}'")
if role_owner_client_id: if client_scope_id:
role_owner_client = kc.get_client_by_client_id(role_owner_client_id, realm) objClientScope = kc.get_client_by_clientid(client_scope_id, realm)
if not role_owner_client: if not objClientScope:
module.fail_json(msg=f"Failed to retrive client '{realm}.{role_owner_client_id}'") module.fail_json(msg=f"Failed to retrive client '{realm}.{client_scope_id}'")
before_role_mapping = kc.get_client_role_scope_from_client(objClient["id"], role_owner_client["id"], realm) before_role_mapping = kc.get_client_role_scope_from_client(objClient["id"], objClientScope["id"], realm)
else: else:
before_role_mapping = kc.get_client_role_scope_from_realm(objClient["id"], realm) before_role_mapping = kc.get_client_role_scope_from_realm(objClient["id"], realm)
if role_owner_client_id: if client_scope_id:
# retrive all role from client_scope # retrive all role from client_scope
client_scope_roles_by_name = kc.get_client_roles_by_id(role_owner_client["id"], realm) client_scope_roles_by_name = kc.get_client_roles_by_id(objClientScope["id"], realm)
else: else:
# retrive all role from realm # retrive all role from realm
client_scope_roles_by_name = kc.get_realm_roles(realm) client_scope_roles_by_name = kc.get_realm_roles(realm)
@@ -230,8 +228,8 @@ def main():
# update desired # update desired
for role_name in role_names: for role_name in role_names:
if role_name not in client_scope_roles_by_name: if role_name not in client_scope_roles_by_name:
if role_owner_client_id: if client_scope_id:
module.fail_json(msg=f"Failed to retrive role '{realm}.{role_owner_client_id}.{role_name}'") module.fail_json(msg=f"Failed to retrive role '{realm}.{client_scope_id}.{role_name}'")
else: else:
module.fail_json(msg=f"Failed to retrive role '{realm}.{role_name}'") module.fail_json(msg=f"Failed to retrive role '{realm}.{role_name}'")
if role_name not in role_mapping_by_name: if role_name not in role_mapping_by_name:
@@ -255,33 +253,33 @@ def main():
if not result["changed"]: if not result["changed"]:
# no changes # no changes
result["end_state"] = before_role_mapping result["end_state"] = before_role_mapping
result["msg"] = f"No changes required for client role scope {target_client_id}." result["msg"] = f"No changes required for client role scope {clientid}."
elif state == "present": elif state == "present":
# doing update # doing update
if module.check_mode: if module.check_mode:
result["end_state"] = desired_role_mapping result["end_state"] = desired_role_mapping
elif role_owner_client_id: elif client_scope_id:
result["end_state"] = kc.update_client_role_scope_from_client( result["end_state"] = kc.update_client_role_scope_from_client(
role_mapping_to_manipulate, objClient["id"], role_owner_client["id"], realm role_mapping_to_manipulate, objClient["id"], objClientScope["id"], realm
) )
else: else:
result["end_state"] = kc.update_client_role_scope_from_realm( result["end_state"] = kc.update_client_role_scope_from_realm(
role_mapping_to_manipulate, objClient["id"], realm role_mapping_to_manipulate, objClient["id"], realm
) )
result["msg"] = f"Client role scope for {target_client_id} has been updated" result["msg"] = f"Client role scope for {clientid} has been updated"
else: else:
# doing delete # doing delete
if module.check_mode: if module.check_mode:
result["end_state"] = desired_role_mapping result["end_state"] = desired_role_mapping
elif role_owner_client_id: elif client_scope_id:
result["end_state"] = kc.delete_client_role_scope_from_client( result["end_state"] = kc.delete_client_role_scope_from_client(
role_mapping_to_manipulate, objClient["id"], role_owner_client["id"], realm role_mapping_to_manipulate, objClient["id"], objClientScope["id"], realm
) )
else: else:
result["end_state"] = kc.delete_client_role_scope_from_realm( result["end_state"] = kc.delete_client_role_scope_from_realm(
role_mapping_to_manipulate, objClient["id"], realm role_mapping_to_manipulate, objClient["id"], realm
) )
result["msg"] = f"Client role scope for {target_client_id} has been deleted" result["msg"] = f"Client role scope for {clientid} has been deleted"
module.exit_json(**result) module.exit_json(**result)

View File

@@ -5,8 +5,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function) from __future__ import absolute_import, division, print_function
from __future__ import annotations
__metaclass__ = type __metaclass__ = type
DOCUMENTATION = ''' DOCUMENTATION = '''
@@ -231,7 +230,7 @@ def main():
attributes = module.params.get('attributes') attributes = module.params.get('attributes')
protocol_mappers = module.params.get('protocol_mappers') protocol_mappers = module.params.get('protocol_mappers')
before_scope = kc.get_client_scope_by_name(name, realm=realm) before_scope = kc.get_clientscope_by_name(name, realm=realm)
if state == 'absent': if state == 'absent':
if before_scope: if before_scope:
@@ -240,7 +239,7 @@ def main():
result['diff'] = dict(before=before_scope, after='') result['diff'] = dict(before=before_scope, after='')
if module.check_mode: if module.check_mode:
module.exit_json(**result) module.exit_json(**result)
kc.delete_client_scope(cid=before_scope['id'], realm=realm) kc.delete_clientscope(cid=before_scope['id'], realm=realm)
result['msg'] = "Client scope {name} has been deleted".format(name=name) result['msg'] = "Client scope {name} has been deleted".format(name=name)
else: else:
result['msg'] = "Client scope {name} does not exist, doing nothing".format(name=name) result['msg'] = "Client scope {name} does not exist, doing nothing".format(name=name)
@@ -262,8 +261,8 @@ def main():
if module.check_mode: if module.check_mode:
module.exit_json(**result) module.exit_json(**result)
kc.create_client_scope(scope_rep, realm=realm) kc.create_clientscope(scope_rep, realm=realm)
after_scope = kc.get_client_scope_by_name(name, realm=realm) after_scope = kc.get_clientscope_by_name(name, realm=realm)
if protocol_mappers: if protocol_mappers:
for mapper in protocol_mappers: for mapper in protocol_mappers:
@@ -273,8 +272,8 @@ def main():
'protocolMapper': mapper['protocolMapper'], 'protocolMapper': mapper['protocolMapper'],
'config': mapper['config'], 'config': mapper['config'],
} }
kc.create_client_scope_protocolmapper(after_scope['id'], mapper_rep, realm=realm) kc.create_clientscope_protocolmapper(after_scope['id'], mapper_rep, realm=realm)
after_scope = kc.get_client_scope_by_name(name, realm=realm) after_scope = kc.get_clientscope_by_name(name, realm=realm)
result['end_state'] = after_scope result['end_state'] = after_scope
result['msg'] = "Client scope {name} has been created".format(name=name) result['msg'] = "Client scope {name} has been created".format(name=name)
@@ -297,10 +296,10 @@ def main():
result['diff'] = dict(before=before_scope, after=scope_rep) result['diff'] = dict(before=before_scope, after=scope_rep)
if module.check_mode: if module.check_mode:
module.exit_json(**result) module.exit_json(**result)
kc.update_client_scope(scope_rep, realm=realm) kc.update_clientscope(scope_rep, realm=realm)
if protocol_mappers: if protocol_mappers:
existing_mappers = kc.get_client_scope_protocolmappers(before_scope['id'], realm=realm) existing_mappers = kc.get_clientscope_protocolmappers(before_scope['id'], realm=realm)
existing_mapper_names = {m['name'] for m in existing_mappers} existing_mapper_names = {m['name'] for m in existing_mappers}
for mapper in protocol_mappers: for mapper in protocol_mappers:
@@ -313,9 +312,9 @@ def main():
'protocolMapper': mapper['protocolMapper'], 'protocolMapper': mapper['protocolMapper'],
'config': mapper['config'], 'config': mapper['config'],
} }
kc.create_client_scope_protocolmapper(before_scope['id'], mapper_rep, realm=realm) kc.create_clientscope_protocolmapper(before_scope['id'], mapper_rep, realm=realm)
after_scope = kc.get_client_scope_by_name(name, realm=realm) after_scope = kc.get_clientscope_by_name(name, realm=realm)
result['end_state'] = after_scope result['end_state'] = after_scope
if result['changed']: if result['changed']:

View File

@@ -3,21 +3,19 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_client_scope_rolemappings module: keycloak_clientscope_rolemappings
short_description: Allows administration of Keycloak client scope scope mappings to restrict the usage of certain roles to short_description: Allows administration of Keycloak clientscope scope mappings to restrict the usage of certain roles to
specific client scopes specific clientscopes
# Originally added in community.general 13.1.0 # Originally added in community.general 13.1.0
version_added: "3.0.0" version_added: "3.0.0"
description: description:
- This module allows you to add or remove Keycloak roles from client scopes using the Keycloak REST API. It requires access - This module allows you to add or remove Keycloak roles from clientscopes using the Keycloak REST API. It requires access
to the REST API using OpenID Connect; the user connecting and the client being used must have the requisite access rights. to the REST API using OpenID Connect; the user connecting and the client being used must have the requisite access rights.
In a default Keycloak installation, C(admin-cli) and an admin user would work, as would a separate client definition with In a default Keycloak installation, C(admin-cli) and an admin user would work, as would a separate client definition with
the scope tailored to your needs and a user having the expected roles. the scope tailored to your needs and a user having the expected roles.
@@ -51,11 +49,11 @@ options:
- The Keycloak realm under which clients resides. - The Keycloak realm under which clients resides.
default: 'master' default: 'master'
client_scope_id: clientscope_id:
required: true required: true
type: str type: str
description: description:
- Roles provided in O(role_names) will be added to this client scope. - Roles provided in O(role_names) will be added to this clientscope.
client_id: client_id:
type: str type: str
@@ -83,40 +81,40 @@ author:
""" """
EXAMPLES = r""" EXAMPLES = r"""
- name: Add roles to client scope - name: Add roles to clientscope
middleware_automation.keycloak.keycloak_client_scope_rolemappings: middleware_automation.keycloak.keycloak_clientscope_rolemappings:
auth_keycloak_url: https://auth.example.com auth_keycloak_url: https://auth.example.com
auth_realm: master auth_realm: master
auth_username: USERNAME auth_username: USERNAME
auth_password: PASSWORD auth_password: PASSWORD
realm: MyCustomRealm realm: MyCustomRealm
client_id: frontend-client-public client_id: frontend-client-public
client_scope_id: frontend-client-scope clientscope_id: frontend-clientscope
role_names: role_names:
- backend-role-admin - backend-role-admin
- backend-role-user - backend-role-user
- name: Remove roles from client scope - name: Remove roles from clientscope
middleware_automation.keycloak.keycloak_client_scope_rolemappings: middleware_automation.keycloak.keycloak_clientscope_rolemappings:
auth_keycloak_url: https://auth.example.com auth_keycloak_url: https://auth.example.com
auth_realm: master auth_realm: master
auth_username: USERNAME auth_username: USERNAME
auth_password: PASSWORD auth_password: PASSWORD
realm: MyCustomRealm realm: MyCustomRealm
client_id: frontend-client-public client_id: frontend-client-public
client_scope_id: frontend-client-scope clientscope_id: frontend-clientscope
role_names: role_names:
- backend-role-admin - backend-role-admin
state: absent state: absent
- name: Add realm roles to client scope - name: Add realm roles to clientscope
middleware_automation.keycloak.keycloak_client_scope_rolemappings: middleware_automation.keycloak.keycloak_clientscope_rolemappings:
auth_keycloak_url: https://auth.example.com auth_keycloak_url: https://auth.example.com
auth_realm: master auth_realm: master
auth_username: USERNAME auth_username: USERNAME
auth_password: PASSWORD auth_password: PASSWORD
realm: MyCustomRealm realm: MyCustomRealm
client_scope_id: frontend-client-scope clientscope_id: frontend-clientscope
role_names: role_names:
- realm-role-admin - realm-role-admin
- realm-role-user - realm-role-user
@@ -124,7 +122,7 @@ EXAMPLES = r"""
RETURN = r""" RETURN = r"""
end_state: end_state:
description: Representation of client scope scope mappings after module execution. description: Representation of clientscope scope mappings after module execution.
returned: on success returned: on success
type: list type: list
elements: dict elements: dict
@@ -166,7 +164,7 @@ def main():
meta_args = dict( meta_args = dict(
client_id=dict(type="str"), client_id=dict(type="str"),
client_scope_id=dict(type="str", required=True), clientscope_id=dict(type="str", required=True),
realm=dict(type="str", default="master"), realm=dict(type="str", default="master"),
role_names=dict(type="list", elements="str", required=True), role_names=dict(type="list", elements="str", required=True),
state=dict(type="str", default="present", choices=["present", "absent"]), state=dict(type="str", default="present", choices=["present", "absent"]),
@@ -188,7 +186,7 @@ def main():
realm = module.params["realm"] realm = module.params["realm"]
client_id = module.params["client_id"] client_id = module.params["client_id"]
client_scope_id = module.params["client_scope_id"] clientscope_id = module.params["clientscope_id"]
role_names = module.params["role_names"] role_names = module.params["role_names"]
state = module.params["state"] state = module.params["state"]
@@ -196,23 +194,23 @@ def main():
if not realm_object: if not realm_object:
module.fail_json(msg=f"Failed to retrieve realm '{realm}'") module.fail_json(msg=f"Failed to retrieve realm '{realm}'")
client_scope_object = kc.get_client_scope_by_name(client_scope_id, realm) clientscope_object = kc.get_clientscope_by_name(clientscope_id, realm)
if not client_scope_object: if not clientscope_object:
module.fail_json(msg=f"Failed to retrieve client scope '{client_scope_id}'") module.fail_json(msg=f"Failed to retrieve client-scope '{clientscope_id}'")
if client_id: if client_id:
# add client role # add client role
client_object = kc.get_client_by_client_id(client_id, realm) client_object = kc.get_client_by_clientid(client_id, realm)
if not client_object: if not client_object:
module.fail_json(msg=f"Failed to retrieve client '{realm}.{client_id}'") module.fail_json(msg=f"Failed to retrieve client '{realm}.{client_id}'")
if client_object["fullScopeAllowed"] and state == "present": if client_object["fullScopeAllowed"] and state == "present":
module.fail_json(msg=f"FullScopeAllowed is active for Client '{realm}.{client_id}'") module.fail_json(msg=f"FullScopeAllowed is active for Client '{realm}.{client_id}'")
before_roles = kc.get_client_scope_scope_mappings_client(client_scope_object["id"], client_object["id"], realm) before_roles = kc.get_clientscope_scope_mappings_client(clientscope_object["id"], client_object["id"], realm)
available_roles_by_name = kc.get_client_roles_by_id(client_object["id"], realm) available_roles_by_name = kc.get_client_roles_by_id(client_object["id"], realm)
else: else:
# add realm role # add realm role
before_roles = kc.get_client_scope_scope_mappings_realm(client_scope_object["id"], realm) before_roles = kc.get_clientscope_scope_mappings_realm(clientscope_object["id"], realm)
available_roles_by_name = kc.get_realm_roles(realm) available_roles_by_name = kc.get_realm_roles(realm)
# convert to indexed Dict by name # convert to indexed Dict by name
@@ -250,33 +248,33 @@ def main():
if not result["changed"]: if not result["changed"]:
# no changes # no changes
result["end_state"] = before_roles result["end_state"] = before_roles
result["msg"] = f"No changes required for client scope {client_scope_id}." result["msg"] = f"No changes required for clientscope {clientscope_id}."
elif state == "present": elif state == "present":
# doing update # doing update
if module.check_mode: if module.check_mode:
result["end_state"] = desired_role_mapping result["end_state"] = desired_role_mapping
elif client_id: elif client_id:
result["end_state"] = kc.update_client_scope_scope_mappings_client( result["end_state"] = kc.update_clientscope_scope_mappings_client(
changed_roles, client_scope_object["id"], client_object["id"], realm changed_roles, clientscope_object["id"], client_object["id"], realm
) )
else: else:
result["end_state"] = kc.update_client_scope_scope_mappings_realm( result["end_state"] = kc.update_clientscope_scope_mappings_realm(
changed_roles, client_scope_object["id"], realm changed_roles, clientscope_object["id"], realm
) )
result["msg"] = f"Clientscope scope mappings for {client_scope_id} have been updated" result["msg"] = f"Clientscope scope mappings for {clientscope_id} have been updated"
else: else:
# doing delete # doing delete
if module.check_mode: if module.check_mode:
result["end_state"] = desired_role_mapping result["end_state"] = desired_role_mapping
elif client_id: elif client_id:
result["end_state"] = kc.delete_client_scope_scope_mappings_client( result["end_state"] = kc.delete_clientscope_scope_mappings_client(
changed_roles, client_scope_object["id"], client_object["id"], realm changed_roles, clientscope_object["id"], client_object["id"], realm
) )
else: else:
result["end_state"] = kc.delete_client_scope_scope_mappings_realm( result["end_state"] = kc.delete_clientscope_scope_mappings_realm(
changed_roles, client_scope_object["id"], realm changed_roles, clientscope_object["id"], realm
) )
result["msg"] = f"Clientscope scope mappings for {client_scope_id} have been deleted" result["msg"] = f"Clientscope scope mappings for {clientscope_id} have been deleted"
module.exit_json(**result) module.exit_json(**result)

View File

@@ -3,20 +3,18 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_client_scope_type module: keycloak_clientscope_type
short_description: Set the type of a client scope in a realm or client using the Keycloak API short_description: Set the type of aclientscope in realm or client using Keycloak API
# Originally added in community.general 6.6.0 # Originally added in community.general 6.6.0
version_added: "3.0.0" version_added: "3.0.0"
description: description:
- This module allows you to set the type (optional, default) of client scopes using the Keycloak REST API. It requires access - This module allows you to set the type (optional, default) of clientscopes using the Keycloak REST API. It requires access
to the REST API using OpenID Connect; the user connecting and the client being used must have the requisite access rights. to the REST API using OpenID Connect; the user connecting and the client being used must have the requisite access rights.
In a default Keycloak installation, admin-cli and an admin user would work, as would a separate client definition with In a default Keycloak installation, admin-cli and an admin user would work, as would a separate client definition with
the scope tailored to your needs and a user having the expected roles. the scope tailored to your needs and a user having the expected roles.
@@ -38,18 +36,18 @@ options:
client_id: client_id:
description: description:
- The O(client_id) of the client. If not set the client scope types are set as a default for the realm. - The O(client_id) of the client. If not set the clientscope types are set as a default for the realm.
aliases: aliases:
- clientId - clientId
type: str type: str
default_client_scopes: default_clientscopes:
description: description:
- Client scopes that should be of type default. - Client scopes that should be of type default.
type: list type: list
elements: str elements: str
optional_client_scopes: optional_clientscopes:
description: description:
- Client scopes that should be of type optional. - Client scopes that should be of type optional.
type: list type: list
@@ -66,26 +64,26 @@ author:
EXAMPLES = r""" EXAMPLES = r"""
- name: Set default client scopes on realm level - name: Set default client scopes on realm level
middleware_automation.keycloak.keycloak_client_scope_type: middleware_automation.keycloak.keycloak_clientscope_type:
auth_client_id: admin-cli auth_client_id: admin-cli
auth_keycloak_url: https://auth.example.com auth_keycloak_url: https://auth.example.com
auth_realm: master auth_realm: master
auth_username: USERNAME auth_username: USERNAME
auth_password: PASSWORD auth_password: PASSWORD
realm: "MyCustomRealm" realm: "MyCustomRealm"
default_client_scopes: ['profile', 'roles'] default_clientscopes: ['profile', 'roles']
delegate_to: localhost delegate_to: localhost
- name: Set default and optional client scopes on client level with token auth - name: Set default and optional client scopes on client level with token auth
middleware_automation.keycloak.keycloak_client_scope_type: middleware_automation.keycloak.keycloak_clientscope_type:
auth_client_id: admin-cli auth_client_id: admin-cli
auth_keycloak_url: https://auth.example.com auth_keycloak_url: https://auth.example.com
token: TOKEN token: TOKEN
realm: "MyCustomRealm" realm: "MyCustomRealm"
client_id: "MyCustomClient" client_id: "MyCustomClient"
default_client_scopes: ['profile', 'roles'] default_clientscopes: ['profile', 'roles']
optional_client_scopes: ['phone'] optional_clientscopes: ['phone']
delegate_to: localhost delegate_to: localhost
""" """
@@ -96,16 +94,16 @@ msg:
type: str type: str
sample: "" sample: ""
proposed: proposed:
description: Representation of proposed client scope types mapping. description: Representation of proposed client-scope types mapping.
returned: always returned: always
type: dict type: dict
sample: sample:
{ {
"default_client_scopes": [ "default_clientscopes": [
"profile", "profile",
"role" "role"
], ],
"optional_client_scopes": [] "optional_clientscopes": []
} }
existing: existing:
description: description:
@@ -114,11 +112,11 @@ existing:
type: dict type: dict
sample: sample:
{ {
"default_client_scopes": [ "default_clientscopes": [
"profile", "profile",
"role" "role"
], ],
"optional_client_scopes": [ "optional_clientscopes": [
"phone" "phone"
] ]
} }
@@ -130,11 +128,11 @@ end_state:
type: dict type: dict
sample: sample:
{ {
"default_client_scopes": [ "default_clientscopes": [
"profile", "profile",
"role" "role"
], ],
"optional_client_scopes": [] "optional_clientscopes": []
} }
""" """
@@ -148,7 +146,7 @@ from ansible_collections.middleware_automation.keycloak.plugins.module_utils.ide
) )
def keycloak_client_scope_type_module(): def keycloak_clientscope_type_module():
""" """
Returns an AnsibleModule definition. Returns an AnsibleModule definition.
@@ -159,8 +157,8 @@ def keycloak_client_scope_type_module():
meta_args = dict( meta_args = dict(
realm=dict(default="master"), realm=dict(default="master"),
client_id=dict(type="str", aliases=["clientId"]), client_id=dict(type="str", aliases=["clientId"]),
default_client_scopes=dict(type="list", elements="str"), default_clientscopes=dict(type="list", elements="str"),
optional_client_scopes=dict(type="list", elements="str"), optional_clientscopes=dict(type="list", elements="str"),
) )
argument_spec.update(meta_args) argument_spec.update(meta_args)
@@ -171,7 +169,7 @@ def keycloak_client_scope_type_module():
required_one_of=( required_one_of=(
[ [
["token", "auth_realm", "auth_username", "auth_password", "auth_client_id", "auth_client_secret"], ["token", "auth_realm", "auth_username", "auth_password", "auth_client_id", "auth_client_secret"],
["default_client_scopes", "optional_client_scopes"], ["default_clientscopes", "optional_clientscopes"],
] ]
), ),
required_together=([["auth_username", "auth_password"]]), required_together=([["auth_username", "auth_password"]]),
@@ -182,21 +180,21 @@ def keycloak_client_scope_type_module():
return module return module
def client_scopes_to_add(existing, proposed): def clientscopes_to_add(existing, proposed):
to_add = [] to_add = []
existing_client_scope_ids = extract_field(existing, "id") existing_clientscope_ids = extract_field(existing, "id")
for client_scope in proposed: for clientscope in proposed:
if client_scope["id"] not in existing_client_scope_ids: if clientscope["id"] not in existing_clientscope_ids:
to_add.append(client_scope) to_add.append(clientscope)
return to_add return to_add
def client_scopes_to_delete(existing, proposed): def clientscopes_to_delete(existing, proposed):
to_delete = [] to_delete = []
proposed_client_scope_ids = extract_field(proposed, "id") proposed_clientscope_ids = extract_field(proposed, "id")
for client_scope in existing: for clientscope in existing:
if client_scope["id"] not in proposed_client_scope_ids: if clientscope["id"] not in proposed_clientscope_ids:
to_delete.append(client_scope) to_delete.append(clientscope)
return to_delete return to_delete
@@ -206,21 +204,21 @@ def extract_field(dictionary, field="name"):
def normalize_scopes(scopes): def normalize_scopes(scopes):
scopes_copy = scopes.copy() scopes_copy = scopes.copy()
if isinstance(scopes_copy.get("default_client_scopes"), list): if isinstance(scopes_copy.get("default_clientscopes"), list):
scopes_copy["default_client_scopes"] = sorted(scopes_copy["default_client_scopes"]) scopes_copy["default_clientscopes"] = sorted(scopes_copy["default_clientscopes"])
if isinstance(scopes_copy.get("optional_client_scopes"), list): if isinstance(scopes_copy.get("optional_clientscopes"), list):
scopes_copy["optional_client_scopes"] = sorted(scopes_copy["optional_client_scopes"]) scopes_copy["optional_clientscopes"] = sorted(scopes_copy["optional_clientscopes"])
return scopes_copy return scopes_copy
def main(): def main():
""" """
Module keycloak_client_scope_type Module keycloak_clientscope_type
:return: :return:
""" """
module = keycloak_client_scope_type_module() module = keycloak_clientscope_type_module()
# Obtain access token, initialize API # Obtain access token, initialize API
try: try:
@@ -232,81 +230,81 @@ def main():
realm = module.params.get("realm") realm = module.params.get("realm")
client_id = module.params.get("client_id") client_id = module.params.get("client_id")
default_client_scopes = module.params.get("default_client_scopes") default_clientscopes = module.params.get("default_clientscopes")
optional_client_scopes = module.params.get("optional_client_scopes") optional_clientscopes = module.params.get("optional_clientscopes")
result = dict(changed=False, msg="", proposed={}, existing={}, end_state={}) result = dict(changed=False, msg="", proposed={}, existing={}, end_state={})
all_client_scopes = kc.get_client_scopes(realm) all_clientscopes = kc.get_clientscopes(realm)
default_client_scopes_real = [] default_clientscopes_real = []
optional_client_scopes_real = [] optional_clientscopes_real = []
for client_scope in all_client_scopes: for client_scope in all_clientscopes:
if default_client_scopes is not None and client_scope["name"] in default_client_scopes: if default_clientscopes is not None and client_scope["name"] in default_clientscopes:
default_client_scopes_real.append(client_scope) default_clientscopes_real.append(client_scope)
if optional_client_scopes is not None and client_scope["name"] in optional_client_scopes: if optional_clientscopes is not None and client_scope["name"] in optional_clientscopes:
optional_client_scopes_real.append(client_scope) optional_clientscopes_real.append(client_scope)
if default_client_scopes is not None and len(default_client_scopes_real) != len(default_client_scopes): if default_clientscopes is not None and len(default_clientscopes_real) != len(default_clientscopes):
module.fail_json(msg="At least one of the default_client_scopes does not exist!") module.fail_json(msg="At least one of the default_clientscopes does not exist!")
if optional_client_scopes is not None and len(optional_client_scopes_real) != len(optional_client_scopes): if optional_clientscopes is not None and len(optional_clientscopes_real) != len(optional_clientscopes):
module.fail_json(msg="At least one of the optional_client_scopes does not exist!") module.fail_json(msg="At least one of the optional_clientscopes does not exist!")
result["proposed"].update( result["proposed"].update(
{ {
"default_client_scopes": "no-change" if default_client_scopes is None else default_client_scopes, "default_clientscopes": "no-change" if default_clientscopes is None else default_clientscopes,
"optional_client_scopes": "no-change" if optional_client_scopes is None else optional_client_scopes, "optional_clientscopes": "no-change" if optional_clientscopes is None else optional_clientscopes,
} }
) )
default_client_scopes_existing = kc.get_default_client_scopes(realm, client_id) default_clientscopes_existing = kc.get_default_clientscopes(realm, client_id)
optional_client_scopes_existing = kc.get_optional_client_scopes(realm, client_id) optional_clientscopes_existing = kc.get_optional_clientscopes(realm, client_id)
result["existing"].update( result["existing"].update(
{ {
"default_client_scopes": extract_field(default_client_scopes_existing), "default_clientscopes": extract_field(default_clientscopes_existing),
"optional_client_scopes": extract_field(optional_client_scopes_existing), "optional_clientscopes": extract_field(optional_clientscopes_existing),
} }
) )
if module._diff: if module._diff:
result["diff"] = dict(before=normalize_scopes(result["existing"]), after=normalize_scopes(result["proposed"])) result["diff"] = dict(before=normalize_scopes(result["existing"]), after=normalize_scopes(result["proposed"]))
default_client_scopes_add = client_scopes_to_add(default_client_scopes_existing, default_client_scopes_real) default_clientscopes_add = clientscopes_to_add(default_clientscopes_existing, default_clientscopes_real)
optional_client_scopes_add = client_scopes_to_add(optional_client_scopes_existing, optional_client_scopes_real) optional_clientscopes_add = clientscopes_to_add(optional_clientscopes_existing, optional_clientscopes_real)
default_client_scopes_delete = client_scopes_to_delete(default_client_scopes_existing, default_client_scopes_real) default_clientscopes_delete = clientscopes_to_delete(default_clientscopes_existing, default_clientscopes_real)
optional_client_scopes_delete = client_scopes_to_delete(optional_client_scopes_existing, optional_client_scopes_real) optional_clientscopes_delete = clientscopes_to_delete(optional_clientscopes_existing, optional_clientscopes_real)
result["changed"] = any( result["changed"] = any(
len(x) > 0 len(x) > 0
for x in [ for x in [
default_client_scopes_add, default_clientscopes_add,
optional_client_scopes_add, optional_clientscopes_add,
default_client_scopes_delete, default_clientscopes_delete,
optional_client_scopes_delete, optional_clientscopes_delete,
] ]
) )
if module.check_mode: if module.check_mode:
module.exit_json(**result) module.exit_json(**result)
# first delete so client_scopes can change type # first delete so clientscopes can change type
for client_scope in default_client_scopes_delete: for clientscope in default_clientscopes_delete:
kc.delete_default_client_scope(client_scope["id"], realm, client_id) kc.delete_default_clientscope(clientscope["id"], realm, client_id)
for client_scope in optional_client_scopes_delete: for clientscope in optional_clientscopes_delete:
kc.delete_optional_client_scope(client_scope["id"], realm, client_id) kc.delete_optional_clientscope(clientscope["id"], realm, client_id)
for client_scope in default_client_scopes_add: for clientscope in default_clientscopes_add:
kc.add_default_client_scope(client_scope["id"], realm, client_id) kc.add_default_clientscope(clientscope["id"], realm, client_id)
for client_scope in optional_client_scopes_add: for clientscope in optional_clientscopes_add:
kc.add_optional_client_scope(client_scope["id"], realm, client_id) kc.add_optional_clientscope(clientscope["id"], realm, client_id)
result["end_state"].update( result["end_state"].update(
{ {
"default_client_scopes": extract_field(kc.get_default_client_scopes(realm, client_id)), "default_clientscopes": extract_field(kc.get_default_clientscopes(realm, client_id)),
"optional_client_scopes": extract_field(kc.get_optional_client_scopes(realm, client_id)), "optional_clientscopes": extract_field(kc.get_optional_clientscopes(realm, client_id)),
} }
) )

View File

@@ -3,9 +3,7 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_clientsecret_info module: keycloak_clientsecret_info

View File

@@ -3,9 +3,7 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_clientsecret_regenerate module: keycloak_clientsecret_regenerate

View File

@@ -3,9 +3,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_clienttemplate module: keycloak_clienttemplate

View File

@@ -3,9 +3,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or
# https://www.gnu.org/licenses/gpl-3.0.txt) # https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_component module: keycloak_component

View File

@@ -3,9 +3,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_component_info module: keycloak_component_info

View File

@@ -3,9 +3,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_group module: keycloak_group

View File

@@ -3,9 +3,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_identity_provider module: keycloak_identity_provider
@@ -117,15 +115,6 @@ options:
- authenticateByDefault - authenticateByDefault
type: bool type: bool
hide_on_login:
description:
- If hidden, login with this provider is possible only if requested explicitly, for example using the C(kc_idp_hint)
parameter.
aliases:
- hideOnLogin
- hide_on_login_page
type: bool
provider_id: provider_id:
description: description:
- Protocol used by this provider (supported values are V(oidc) or V(saml)). - Protocol used by this provider (supported values are V(oidc) or V(saml)).
@@ -140,6 +129,14 @@ options:
identity provider configuration through check-mode in the RV(existing) field. identity provider configuration through check-mode in the RV(existing) field.
type: dict type: dict
suboptions: suboptions:
hide_on_login_page:
description:
- If hidden, login with this provider is possible only if requested explicitly, for example using the C(kc_idp_hint)
parameter.
aliases:
- hideOnLoginPage
type: bool
gui_order: gui_order:
description: description:
- Number defining order of the provider in GUI (for example, on Login page). - Number defining order of the provider in GUI (for example, on Login page).
@@ -572,7 +569,6 @@ def main():
alias=dict(type="str", required=True), alias=dict(type="str", required=True),
add_read_token_role_on_create=dict(type="bool", aliases=["addReadTokenRoleOnCreate"]), add_read_token_role_on_create=dict(type="bool", aliases=["addReadTokenRoleOnCreate"]),
authenticate_by_default=dict(type="bool", aliases=["authenticateByDefault"]), authenticate_by_default=dict(type="bool", aliases=["authenticateByDefault"]),
hide_on_login=dict(type="bool", aliases=["hideOnLogin", "hide_on_login_page"]),
config=dict(type="dict"), config=dict(type="dict"),
display_name=dict(type="str", aliases=["displayName"]), display_name=dict(type="str", aliases=["displayName"]),
enabled=dict(type="bool"), enabled=dict(type="bool"),
@@ -612,20 +608,6 @@ def main():
state = module.params.get("state") state = module.params.get("state")
config = module.params.get("config") config = module.params.get("config")
if config is not None:
for legacy_key in ("hide_on_login_page", "hideOnLoginPage"):
if legacy_key in config:
module.deprecate(
f"Passing '{legacy_key}' inside 'config' is deprecated. "
"Use the top-level 'hide_on_login' parameter instead.",
version="4.0.0",
collection_name="middleware_automation.keycloak",
)
if module.params.get("hide_on_login") is None:
module.params["hide_on_login"] = config.pop(legacy_key)
else:
config.pop(legacy_key)
fetch_identity_provider_wellknown_config(kc, config) fetch_identity_provider_wellknown_config(kc, config)
# Filter and map the parameters names that apply to the identity provider. # Filter and map the parameters names that apply to the identity provider.

View File

@@ -4,9 +4,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_realm module: keycloak_realm

View File

@@ -3,9 +3,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_realm_info module: keycloak_realm_info

View File

@@ -4,9 +4,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or
# https://www.gnu.org/licenses/gpl-3.0.txt) # https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_realm_key module: keycloak_realm_key

View File

@@ -3,9 +3,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_realm_keys_metadata_info module: keycloak_realm_keys_metadata_info

View File

@@ -4,9 +4,7 @@
# https://www.gnu.org/licenses/gpl-3.0.txt) # https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_realm_localization module: keycloak_realm_localization

View File

@@ -3,9 +3,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_realm_rolemapping module: keycloak_realm_rolemapping

View File

@@ -3,9 +3,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_role module: keycloak_role

View File

@@ -3,9 +3,7 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_user module: keycloak_user

View File

@@ -3,9 +3,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_user_execute_actions_email module: keycloak_user_execute_actions_email

View File

@@ -3,9 +3,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_user_federation module: keycloak_user_federation

View File

@@ -2,9 +2,7 @@
# Copyright (c) 2022, Dušan Marković (@bratwurzt) # Copyright (c) 2022, Dušan Marković (@bratwurzt)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_user_rolemapping module: keycloak_user_rolemapping

View File

@@ -3,9 +3,7 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from __future__ import annotations from __future__ import annotations
__metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r"""
module: keycloak_userprofile module: keycloak_userprofile

View File

@@ -2,14 +2,4 @@
collections: collections:
- name: middleware_automation.common - name: middleware_automation.common
version: ">=1.2.4" version: ">=1.2.4"
- name: middleware_automation.infinispan
- name: community.general
- name: ansible.posix - name: ansible.posix
- name: community.docker
version: ">=3.8.0"
- name: containers.podman
version: ">=1.8.1"
roles:
- name: elan.simple_nginx_reverse_proxy
version: "0.2.1"

View File

@@ -388,6 +388,6 @@ argument_specs:
description: "Red Hat SSO patch archive filename" description: "Red Hat SSO patch archive filename"
type: "str" type: "str"
sso_product_category: sso_product_category:
default: "CORE.SERVICE.RHSSO" default: "core.service.rhsso"
description: "Unified Downloads API category for Single Sign-On" description: "JBossNetwork API category for Single Sign-On"
type: "str" type: "str"

View File

@@ -1,24 +1,24 @@
--- ---
- name: "Check if packages are already installed" # noqa command-instead-of-module this runs faster - name: "Check if packages are already installed" # noqa command-instead-of-module this runs faster
ansible.builtin.command: "rpm -q {{ packages_list | join(' ') }}" ansible.builtin.command: "rpm -q {{ packages_list | join(' ') }}"
register: keycloak_rpm_info register: rpm_info
changed_when: false changed_when: false
failed_when: false failed_when: false
when: ansible_facts.os_family == "RedHat" when: ansible_facts.os_family == "RedHat"
- name: "Add missing packages to the yum install list" - name: "Add missing packages to the yum install list"
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_packages_to_install: "{{ keycloak_packages_to_install | default([]) + keycloak_rpm_info.stdout_lines | \ packages_to_install: "{{ packages_to_install | default([]) + rpm_info.stdout_lines | \
map('regex_findall', 'package (.+) is not installed$') | default([]) | flatten }}" map('regex_findall', 'package (.+) is not installed$') | default([]) | flatten }}"
when: ansible_facts.os_family == "RedHat" when: ansible_facts.os_family == "RedHat"
- name: "Install packages: {{ keycloak_packages_to_install }}" - name: "Install packages: {{ packages_to_install }}"
become: "{{ keycloak_fastpackages_require_privilege_escalation | default(true) }}" become: "{{ keycloak_fastpackages_require_privilege_escalation | default(true) }}"
ansible.builtin.dnf: ansible.builtin.dnf:
name: "{{ keycloak_packages_to_install }}" name: "{{ packages_to_install }}"
state: present state: present
when: when:
- keycloak_packages_to_install | default([]) | length > 0 - packages_to_install | default([]) | length > 0
- ansible_facts.os_family == "RedHat" - ansible_facts.os_family == "RedHat"
- name: "Install packages: {{ packages_list }}" - name: "Install packages: {{ packages_list }}"

View File

@@ -12,7 +12,7 @@
enabled: true enabled: true
state: started state: started
- name: "Configure firewall ports for {{ keycloak_config.service_name }}" - name: "Configure firewall ports for {{ keycloak.service_name }}"
become: "{{ keycloak_firewalld_require_privilege_escalation | default(true) }}" become: "{{ keycloak_firewalld_require_privilege_escalation | default(true) }}"
ansible.posix.firewalld: ansible.posix.firewalld:
port: "{{ item }}" port: "{{ item }}"

View File

@@ -14,18 +14,18 @@
become: "{{ keycloak_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_install_require_privilege_escalation | default(true) }}"
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ keycloak_jboss_home }}" path: "{{ keycloak_jboss_home }}"
register: keycloak_existing_deploy register: existing_deploy
- name: Stop and restart if existing deployment exists and install forced - name: Stop and restart if existing deployment exists and install forced
when: keycloak_existing_deploy.stat.exists and keycloak_force_install | bool when: existing_deploy.stat.exists and keycloak_force_install | bool
block: block:
- name: "Stop the old service: {{ keycloak_config.service_name }}" - name: "Stop the old {{ keycloak.service_name }} service"
become: "{{ keycloak_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_install_require_privilege_escalation | default(true) }}"
failed_when: false failed_when: false
ansible.builtin.systemd: ansible.builtin.systemd:
name: keycloak name: keycloak
state: stopped state: stopped
- name: "Remove the old deployment: {{ keycloak_config.service_name }}" - name: "Remove the old {{ keycloak.service_name }} deployment"
become: "{{ keycloak_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_install_require_privilege_escalation | default(true) }}"
ansible.builtin.file: ansible.builtin.file:
path: "{{ keycloak_jboss_home }}" path: "{{ keycloak_jboss_home }}"
@@ -36,7 +36,7 @@
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ keycloak_jboss_home }}" path: "{{ keycloak_jboss_home }}"
- name: "Create service user/group for {{ keycloak_config.service_name }}" - name: "Create service user/group for {{ keycloak.service_name }}"
become: "{{ keycloak_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_install_require_privilege_escalation | default(true) }}"
ansible.builtin.user: ansible.builtin.user:
name: "{{ keycloak_service_user }}" name: "{{ keycloak_service_user }}"
@@ -44,7 +44,7 @@
system: true system: true
create_home: false create_home: false
- name: "Create install location for {{ keycloak_config.service_name }}" - name: "Create install location for {{ keycloak.service_name }}"
become: "{{ keycloak_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_install_require_privilege_escalation | default(true) }}"
ansible.builtin.file: ansible.builtin.file:
dest: "{{ keycloak_dest }}" dest: "{{ keycloak_dest }}"
@@ -65,35 +65,34 @@
## check remote archive ## check remote archive
- name: Set download archive path - name: Set download archive path
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_archive_full_path: "{{ keycloak_dest }}/{{ keycloak_archive }}" archive: "{{ keycloak_dest }}/{{ keycloak.bundle }}"
- name: Check download archive path - name: Check download archive path
become: "{{ keycloak_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_install_require_privilege_escalation | default(true) }}"
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ keycloak_archive_full_path }}" path: "{{ archive }}"
register: keycloak_archive_path register: archive_path
## download to controller ## download to controller
- name: Check local download archive path - name: Check local download archive path
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ lookup('env', 'PWD') }}" path: "{{ lookup('env', 'PWD') }}"
register: keycloak_local_path register: local_path
delegate_to: localhost delegate_to: localhost
- name: Download keycloak archive - name: Download keycloak archive
ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user
url: "{{ keycloak_download_url }}" url: "{{ keycloak_download_url }}"
dest: "{{ keycloak_local_path.stat.path }}/{{ keycloak_archive }}" dest: "{{ local_path.stat.path }}/{{ keycloak.bundle }}"
mode: '0644' mode: '0644'
url_username: "{{ keycloak_binary_download_user | default(omit) }}" url_username: "{{ keycloak_binary_download_user | default(omit) }}"
url_password: "{{ keycloak_binary_download_pass | default(omit) }}" url_password: "{{ keycloak_binary_download_pass | default(omit) }}"
validate_certs: false
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
when: when:
- keycloak_archive_path is defined - archive_path is defined
- keycloak_archive_path.stat is defined - archive_path.stat is defined
- not keycloak_archive_path.stat.exists - not archive_path.stat.exists
- not sso_enable is defined or not sso_enable - not sso_enable is defined or not sso_enable
- not keycloak_offline_install - not keycloak_offline_install
@@ -101,9 +100,9 @@
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
when: when:
- keycloak_archive_path is defined - archive_path is defined
- keycloak_archive_path.stat is defined - archive_path.stat is defined
- not keycloak_archive_path.stat.exists - not archive_path.stat.exists
- sso_enable is defined and sso_enable - sso_enable is defined and sso_enable
- not keycloak_offline_install - not keycloak_offline_install
block: block:
@@ -114,18 +113,14 @@
product_type: DISTRIBUTION product_type: DISTRIBUTION
product_version: "{{ sso_version.split('.')[:2] | join('.') }}" product_version: "{{ sso_version.split('.')[:2] | join('.') }}"
product_category: "{{ sso_product_category }}" product_category: "{{ sso_product_category }}"
validate_certs: false register: rhn_products
register: keycloak_rhn_products
no_log: "{{ omit_rhn_output | default(true) }}" no_log: "{{ omit_rhn_output | default(true) }}"
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
- name: Determine install zipfile from search results - name: Determine install zipfile from search results
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_rhn_filtered_products: >- rhn_filtered_products: "{{ rhn_products.results | selectattr('file_name', 'match', '[^/]*/' + sso_archive + '$') }}"
{{ keycloak_rhn_products.results
| selectattr('file_name', 'search', 'rh-sso-' + sso_version + '-server-dist.zip$')
| list }}
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
@@ -133,9 +128,8 @@
middleware_automation.common.product_download: # noqa risky-file-permissions delegated, uses controller host user middleware_automation.common.product_download: # noqa risky-file-permissions delegated, uses controller host user
client_id: "{{ rhn_username }}" client_id: "{{ rhn_username }}"
client_secret: "{{ rhn_password }}" client_secret: "{{ rhn_password }}"
product_id: "{{ (keycloak_rhn_filtered_products | first).id }}" product_id: "{{ (rhn_filtered_products | first).id }}"
dest: "{{ keycloak_local_path.stat.path }}/{{ keycloak_archive }}" dest: "{{ local_path.stat.path }}/{{ keycloak.bundle }}"
validate_certs: false
no_log: "{{ omit_rhn_output | default(true) }}" no_log: "{{ omit_rhn_output | default(true) }}"
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
@@ -143,69 +137,68 @@
- name: Download rhsso archive from alternate location - name: Download rhsso archive from alternate location
ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user
url: "{{ keycloak_rhsso_download_url }}" url: "{{ keycloak_rhsso_download_url }}"
dest: "{{ keycloak_local_path.stat.path }}/{{ keycloak_archive }}" dest: "{{ local_path.stat.path }}/{{ keycloak.bundle }}"
mode: '0644' mode: '0644'
validate_certs: false
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
when: when:
- keycloak_archive_path is defined - archive_path is defined
- keycloak_archive_path.stat is defined - archive_path.stat is defined
- not keycloak_archive_path.stat.exists - not archive_path.stat.exists
- sso_enable is defined and sso_enable - sso_enable is defined and sso_enable
- not keycloak_offline_install - not keycloak_offline_install
- keycloak_rhsso_download_url is defined - keycloak_rhsso_download_url is defined
- name: Check downloaded archive - name: Check downloaded archive
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ keycloak_local_path.stat.path }}/{{ keycloak_archive }}" path: "{{ local_path.stat.path }}/{{ keycloak.bundle }}"
register: keycloak_local_archive_path register: local_archive_path
delegate_to: localhost delegate_to: localhost
## copy and unpack ## copy and unpack
- name: Copy archive to target nodes - name: Copy archive to target nodes
ansible.builtin.copy: ansible.builtin.copy:
src: "{{ keycloak_local_path.stat.path }}/{{ keycloak_archive }}" src: "{{ local_path.stat.path }}/{{ keycloak.bundle }}"
dest: "{{ keycloak_archive_full_path }}" dest: "{{ archive }}"
owner: "{{ keycloak_service_user }}" owner: "{{ keycloak_service_user }}"
group: "{{ keycloak_service_group }}" group: "{{ keycloak_service_group }}"
mode: '0640' mode: '0640'
register: keycloak_new_version_downloaded register: new_version_downloaded
when: when:
- not keycloak_archive_path.stat.exists - not archive_path.stat.exists
- keycloak_local_archive_path.stat is defined - local_archive_path.stat is defined
- keycloak_local_archive_path.stat.exists - local_archive_path.stat.exists
become: "{{ keycloak_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_install_require_privilege_escalation | default(true) }}"
- name: "Check target directory: {{ keycloak_config.home }}" - name: "Check target directory: {{ keycloak.home }}"
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ keycloak_config.home }}" path: "{{ keycloak.home }}"
register: keycloak_path_to_workdir register: path_to_workdir
become: "{{ keycloak_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_install_require_privilege_escalation | default(true) }}"
- name: "Extract archive on target: {{ keycloak_service_desc }}" - name: "Extract {{ keycloak_service_desc }} archive on target"
ansible.builtin.unarchive: ansible.builtin.unarchive:
remote_src: true remote_src: true
src: "{{ keycloak_archive_full_path }}" src: "{{ archive }}"
dest: "{{ keycloak_dest }}" dest: "{{ keycloak_dest }}"
creates: "{{ keycloak_config.home }}" creates: "{{ keycloak.home }}"
owner: "{{ keycloak_service_user }}" owner: "{{ keycloak_service_user }}"
group: "{{ keycloak_service_group }}" group: "{{ keycloak_service_group }}"
become: "{{ keycloak_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_install_require_privilege_escalation | default(true) }}"
when: when:
- keycloak_new_version_downloaded.changed or not keycloak_path_to_workdir.stat.exists - new_version_downloaded.changed or not path_to_workdir.stat.exists
notify: notify:
- restart keycloak - restart keycloak
- name: Inform decompression was not executed - name: Inform decompression was not executed
ansible.builtin.debug: ansible.builtin.debug:
msg: "{{ keycloak_config.home }} already exists and version unchanged, skipping decompression" msg: "{{ keycloak.home }} already exists and version unchanged, skipping decompression"
when: when:
- not keycloak_new_version_downloaded.changed and keycloak_path_to_workdir.stat.exists - not new_version_downloaded.changed and path_to_workdir.stat.exists
- name: "Reown installation directory to {{ keycloak_service_user }}" - name: "Reown installation directory to {{ keycloak_service_user }}"
ansible.builtin.file: ansible.builtin.file:
path: "{{ keycloak_config.home }}" path: "{{ keycloak.home }}"
owner: "{{ keycloak_service_user }}" owner: "{{ keycloak_service_user }}"
group: "{{ keycloak_service_group }}" group: "{{ keycloak_service_group }}"
recurse: true recurse: true
@@ -213,17 +206,17 @@
changed_when: false changed_when: false
- name: Ensure permissions are correct on existing deploy - name: Ensure permissions are correct on existing deploy
ansible.builtin.command: chown -R "{{ keycloak_service_user }}:{{ keycloak_service_group }}" "{{ keycloak_config.home }}" ansible.builtin.command: chown -R "{{ keycloak_service_user }}:{{ keycloak_service_group }}" "{{ keycloak.home }}"
when: keycloak_service_runas when: keycloak_service_runas
become: "{{ keycloak_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_install_require_privilege_escalation | default(true) }}"
changed_when: false changed_when: false
# driver and configuration # driver and configuration
- name: "Install database driver: {{ keycloak_jdbc_engine }}" - name: "Install {{ keycloak_jdbc_engine }} driver"
ansible.builtin.include_tasks: jdbc_driver.yml ansible.builtin.include_tasks: jdbc_driver.yml
when: keycloak_jdbc[keycloak_jdbc_engine].enabled when: keycloak_jdbc[keycloak_jdbc_engine].enabled
- name: "Deploy custom config: {{ keycloak_config.service_name }}" - name: "Deploy custom {{ keycloak.service_name }} config to {{ keycloak_config_path_to_standalone_xml }} from {{ keycloak_config_override_template }}"
become: "{{ keycloak_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_install_require_privilege_escalation | default(true) }}"
ansible.builtin.template: ansible.builtin.template:
src: "templates/{{ keycloak_config_override_template }}" src: "templates/{{ keycloak_config_override_template }}"
@@ -233,9 +226,9 @@
mode: '0640' mode: '0640'
notify: notify:
- restart keycloak - restart keycloak
when: (keycloak_config_override_template | default('', true)) | length > 0 when: keycloak_config_override_template | length > 0
- name: "Deploy standalone config: {{ keycloak_config.service_name }}" - name: "Deploy standalone {{ keycloak.service_name }} config to {{ keycloak_config_path_to_standalone_xml }}"
become: "{{ keycloak_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_install_require_privilege_escalation | default(true) }}"
ansible.builtin.template: ansible.builtin.template:
src: templates/standalone.xml.j2 src: templates/standalone.xml.j2
@@ -247,7 +240,7 @@
- restart keycloak - restart keycloak
when: when:
- not keycloak_ha_enabled - not keycloak_ha_enabled
- (keycloak_config_override_template | default('', true)) | length == 0 - keycloak_config_override_template | length == 0
- name: Create tcpping cluster node list - name: Create tcpping cluster node list
ansible.builtin.set_fact: ansible.builtin.set_fact:
@@ -263,7 +256,7 @@
loop: "{{ ansible_play_batch }}" loop: "{{ ansible_play_batch }}"
when: keycloak_ha_enabled and keycloak_ha_discovery == 'TCPPING' when: keycloak_ha_enabled and keycloak_ha_discovery == 'TCPPING'
- name: "Deploy HA config: {{ keycloak_config.service_name }}" - name: "Deploy HA {{ keycloak.service_name }} config to {{ keycloak_config_path_to_standalone_xml }}"
become: "{{ keycloak_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_install_require_privilege_escalation | default(true) }}"
ansible.builtin.template: ansible.builtin.template:
src: templates/standalone-ha.xml.j2 src: templates/standalone-ha.xml.j2
@@ -276,9 +269,9 @@
when: when:
- keycloak_ha_enabled - keycloak_ha_enabled
- not keycloak_remote_cache_enabled - not keycloak_remote_cache_enabled
- (keycloak_config_override_template | default('', true)) | length == 0 - keycloak_config_override_template | length == 0
- name: "Deploy HA config with infinispan: {{ keycloak_config.service_name }}" - name: "Deploy HA {{ keycloak.service_name }} config with infinispan remote cache store to {{ keycloak_config_path_to_standalone_xml }}"
become: "{{ keycloak_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_install_require_privilege_escalation | default(true) }}"
ansible.builtin.template: ansible.builtin.template:
src: templates/standalone-infinispan.xml.j2 src: templates/standalone-infinispan.xml.j2
@@ -291,7 +284,7 @@
when: when:
- keycloak_ha_enabled - keycloak_ha_enabled
- keycloak_remote_cache_enabled - keycloak_remote_cache_enabled
- (keycloak_config_override_template | default('', true)) | length == 0 - keycloak_config_override_template | length == 0
- name: "Deploy profile.properties file to {{ keycloak_config_path_to_properties }}" - name: "Deploy profile.properties file to {{ keycloak_config_path_to_properties }}"
become: "{{ keycloak_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_install_require_privilege_escalation | default(true) }}"
@@ -303,4 +296,4 @@
mode: '0640' mode: '0640'
notify: notify:
- restart keycloak - restart keycloak
when: (keycloak_features | default([], true)) | length > 0 when: keycloak_features | length > 0

View File

@@ -5,7 +5,7 @@
packages_list: packages_list:
- iptables - iptables
- name: "Configure firewall ports for {{ keycloak_config.service_name }}" - name: "Configure firewall ports for {{ keycloak.service_name }}"
become: "{{ keycloak_iptables_require_privilege_escalation | default(true) }}" become: "{{ keycloak_iptables_require_privilege_escalation | default(true) }}"
ansible.builtin.iptables: ansible.builtin.iptables:
destination_port: "{{ item }}" destination_port: "{{ item }}"

View File

@@ -2,7 +2,7 @@
- name: "Check module directory: {{ keycloak_jdbc[keycloak_jdbc_engine].driver_module_dir }}" - name: "Check module directory: {{ keycloak_jdbc[keycloak_jdbc_engine].driver_module_dir }}"
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ keycloak_jdbc[keycloak_jdbc_engine].driver_module_dir }}" path: "{{ keycloak_jdbc[keycloak_jdbc_engine].driver_module_dir }}"
register: keycloak_dest_path register: dest_path
become: "{{ keycloak_jdbc_driver_require_privilege_escalation | default(true) }}" become: "{{ keycloak_jdbc_driver_require_privilege_escalation | default(true) }}"
- name: "Set up module dir for JDBC Driver {{ keycloak_jdbc[keycloak_jdbc_engine].driver_module_name }}" - name: "Set up module dir for JDBC Driver {{ keycloak_jdbc[keycloak_jdbc_engine].driver_module_name }}"

View File

@@ -66,7 +66,7 @@
retries: 2 retries: 2
delay: 2 delay: 2
rescue: rescue:
- name: "Create admin user: {{ keycloak_config.service_name }}" - name: "Create {{ keycloak.service_name }} admin user"
ansible.builtin.command: ansible.builtin.command:
args: args:
argv: argv:
@@ -76,11 +76,11 @@
- "-p{{ keycloak_admin_password }}" - "-p{{ keycloak_admin_password }}"
changed_when: true changed_when: true
become: "{{ keycloak_require_privilege_escalation | default(true) }}" become: "{{ keycloak_require_privilege_escalation | default(true) }}"
- name: "Restart service: {{ keycloak_config.service_name }}" - name: "Restart {{ keycloak.service_name }}"
ansible.builtin.include_tasks: tasks/restart_keycloak.yml ansible.builtin.include_tasks: tasks/restart_keycloak.yml
- name: "Wait until service becomes active: {{ keycloak_config.service_name }}" - name: "Wait until {{ keycloak.service_name }} becomes active {{ keycloak.health_url }}"
ansible.builtin.uri: ansible.builtin.uri:
url: "{{ keycloak_config.health_url }}" url: "{{ keycloak.health_url }}"
register: keycloak_status register: keycloak_status
until: keycloak_status.status == 200 until: keycloak_status.status == 200
retries: 25 retries: 25

View File

@@ -2,7 +2,7 @@
- name: Validate admin console password - name: Validate admin console password
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- (keycloak_admin_password | default('', true)) | length > 12 - keycloak_admin_password | length > 12
quiet: true quiet: true
fail_msg: > fail_msg: >
The console administrator password is empty or invalid. Please set the keycloak_admin_password variable to a 12+ char long string The console administrator password is empty or invalid. Please set the keycloak_admin_password variable to a 12+ char long string
@@ -31,9 +31,9 @@
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- keycloak_jdbc_engine is defined and keycloak_jdbc_engine in [ 'postgres', 'mariadb', 'sqlserver' ] - keycloak_jdbc_engine is defined and keycloak_jdbc_engine in [ 'postgres', 'mariadb', 'sqlserver' ]
- (keycloak_jdbc_url | default('', true)) | length > 0 - keycloak_jdbc_url | length > 0
- (keycloak_db_user | default('', true)) | length > 0 - keycloak_db_user | length > 0
- (keycloak_db_pass | default('', true)) | length > 0 - keycloak_db_pass | length > 0
quiet: true quiet: true
fail_msg: "Configuration for the JDBC persistence is invalid or incomplete" fail_msg: "Configuration for the JDBC persistence is invalid or incomplete"
success_msg: "Configuring JDBC persistence using {{ keycloak_jdbc_engine }} database" success_msg: "Configuring JDBC persistence using {{ keycloak_jdbc_engine }} database"

View File

@@ -1,5 +1,5 @@
--- ---
- name: "Restart and enable service: {{ keycloak_config.service_name }}" - name: "Restart and enable {{ keycloak.service_name }} service"
ansible.builtin.systemd: ansible.builtin.systemd:
name: keycloak name: keycloak
enabled: true enabled: true
@@ -9,9 +9,9 @@
delegate_to: "{{ ansible_play_hosts | first }}" delegate_to: "{{ ansible_play_hosts | first }}"
run_once: true run_once: true
- name: "Wait until service becomes active: {{ keycloak_config.service_name }}" - name: "Wait until {{ keycloak.service_name }} becomes active {{ keycloak.health_url }}"
ansible.builtin.uri: ansible.builtin.uri:
url: "{{ keycloak_config.health_url }}" url: "{{ keycloak.health_url }}"
register: keycloak_status register: keycloak_status
until: keycloak_status.status == 200 until: keycloak_status.status == 200
delegate_to: "{{ ansible_play_hosts | first }}" delegate_to: "{{ ansible_play_hosts | first }}"
@@ -19,7 +19,7 @@
retries: "{{ keycloak_service_start_retries }}" retries: "{{ keycloak_service_start_retries }}"
delay: "{{ keycloak_service_start_delay }}" delay: "{{ keycloak_service_start_delay }}"
- name: "Restart and enable service (remaining nodes): {{ keycloak_config.service_name }}" - name: "Restart and enable {{ keycloak.service_name }} service"
ansible.builtin.systemd: ansible.builtin.systemd:
name: keycloak name: keycloak
enabled: true enabled: true

View File

@@ -8,6 +8,6 @@
- name: "Execute CLI query: {{ cli_query }}" - name: "Execute CLI query: {{ cli_query }}"
ansible.builtin.command: > ansible.builtin.command: >
{{ keycloak_config.cli_path }} --connect --command='{{ cli_query }}' --controller={{ keycloak_host }}:{{ keycloak_management_http_port }} {{ keycloak.cli_path }} --connect --command='{{ cli_query }}' --controller={{ keycloak_host }}:{{ keycloak_management_http_port }}
changed_when: false changed_when: false
register: keycloak_cli_result register: cli_result

View File

@@ -2,15 +2,15 @@
## check remote patch archive ## check remote patch archive
- name: Set download patch archive path - name: Set download patch archive path
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_patch_archive: "{{ keycloak_dest }}/{{ sso_patch_bundle }}" patch_archive: "{{ keycloak_dest }}/{{ sso_patch_bundle }}"
keycloak_patch_bundle: "{{ sso_patch_bundle }}" patch_bundle: "{{ sso_patch_bundle }}"
keycloak_patch_version: "{{ sso_patch_version }}" patch_version: "{{ sso_patch_version }}"
when: sso_patch_version is defined when: sso_patch_version is defined
- name: Check download patch archive path - name: Check download patch archive path
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ keycloak_patch_archive }}" path: "{{ patch_archive }}"
register: keycloak_patch_archive_path register: patch_archive_path
when: sso_patch_version is defined when: sso_patch_version is defined
become: "{{ keycloak_rhsso_patch_require_privilege_escalation | default(true) }}" become: "{{ keycloak_rhsso_patch_require_privilege_escalation | default(true) }}"
@@ -29,42 +29,41 @@
product_type: BUGFIX product_type: BUGFIX
product_version: "{{ sso_version.split('.')[:2] | join('.') }}" product_version: "{{ sso_version.split('.')[:2] | join('.') }}"
product_category: "{{ sso_product_category }}" product_category: "{{ sso_product_category }}"
validate_certs: false register: rhn_products
register: keycloak_rhn_products
no_log: "{{ omit_rhn_output | default(true) }}" no_log: "{{ omit_rhn_output | default(true) }}"
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
- name: Determine patch versions list - name: Determine patch versions list
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_filtered_versions: "{{ keycloak_rhn_products.results | map(attribute='file_name') | \ filtered_versions: "{{ rhn_products.results | map(attribute='file_name') | \
select('match', '^[^/]*/rh-sso-.*[0-9]*[.][0-9]*[.][0-9]*.*$') | \ select('match', '^[^/]*/rh-sso-.*[0-9]*[.][0-9]*[.][0-9]*.*$') | \
map('regex_replace', '[^/]*/rh-sso-([0-9]*[.][0-9]*[.][0-9]*(-[0-9])?)-.*', '\\1') | list | unique }}" map('regex_replace', '[^/]*/rh-sso-([0-9]*[.][0-9]*[.][0-9]*(-[0-9])?)-.*', '\\1') | list | unique }}"
when: (sso_patch_version | default('', true)) | length == 0 when: sso_patch_version is not defined or sso_patch_version | length == 0
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
- name: Determine latest version - name: Determine latest version
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_sso_latest_version: "{{ keycloak_filtered_versions | middleware_automation.common.version_sort | last }}" sso_latest_version: "{{ filtered_versions | middleware_automation.common.version_sort | last }}"
when: (sso_patch_version | default('', true)) | length == 0 when: sso_patch_version is not defined or sso_patch_version | length == 0
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
- name: Determine install zipfile from search results - name: Determine install zipfile from search results
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_rhn_filtered_products: "{{ keycloak_rhn_products.results | selectattr('file_name', 'match', '[^/]*/rh-sso-' + keycloak_sso_latest_version + '-patch.zip$') }}" rhn_filtered_products: "{{ rhn_products.results | selectattr('file_name', 'match', '[^/]*/rh-sso-' + sso_latest_version + '-patch.zip$') }}"
keycloak_patch_bundle: "rh-sso-{{ keycloak_sso_latest_version }}-patch.zip" patch_bundle: "rh-sso-{{ sso_latest_version }}-patch.zip"
keycloak_patch_version: "{{ keycloak_sso_latest_version }}" patch_version: "{{ sso_latest_version }}"
when: (sso_patch_version | default('', true)) | length == 0 when: sso_patch_version is not defined or sso_patch_version | length == 0
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
- name: "Determine selected patch from supplied version: {{ sso_patch_version }}" - name: "Determine selected patch from supplied version: {{ sso_patch_version }}"
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_rhn_filtered_products: "{{ keycloak_rhn_products.results | selectattr('file_name', 'match', '[^/]*/' + sso_patch_bundle + '$') }}" rhn_filtered_products: "{{ rhn_products.results | selectattr('file_name', 'match', '[^/]*/' + sso_patch_bundle + '$') }}"
keycloak_patch_bundle: "{{ sso_patch_bundle }}" patch_bundle: "{{ sso_patch_bundle }}"
keycloak_patch_version: "{{ sso_patch_version }}" patch_version: "{{ sso_patch_version }}"
when: sso_patch_version is defined when: sso_patch_version is defined
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
@@ -73,36 +72,35 @@
middleware_automation.common.product_download: # noqa risky-file-permissions delegated, uses controller host user middleware_automation.common.product_download: # noqa risky-file-permissions delegated, uses controller host user
client_id: "{{ rhn_username }}" client_id: "{{ rhn_username }}"
client_secret: "{{ rhn_password }}" client_secret: "{{ rhn_password }}"
product_id: "{{ (keycloak_rhn_filtered_products | sort | last).id }}" product_id: "{{ (rhn_filtered_products | sort | last).id }}"
dest: "{{ keycloak_local_path.stat.path }}/{{ keycloak_patch_bundle }}" dest: "{{ local_path.stat.path }}/{{ patch_bundle }}"
validate_certs: false
no_log: "{{ omit_rhn_output | default(true) }}" no_log: "{{ omit_rhn_output | default(true) }}"
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
- name: Set download patch archive path - name: Set download patch archive path
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_patch_archive: "{{ keycloak_dest }}/{{ keycloak_patch_bundle }}" patch_archive: "{{ keycloak_dest }}/{{ patch_bundle }}"
- name: Check download patch archive path - name: Check download patch archive path
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ keycloak_patch_archive }}" path: "{{ patch_archive }}"
register: keycloak_patch_archive_path register: patch_archive_path
become: "{{ keycloak_rhsso_patch_require_privilege_escalation | default(true) }}" become: "{{ keycloak_rhsso_patch_require_privilege_escalation | default(true) }}"
## copy and unpack ## copy and unpack
- name: Copy patch archive to target nodes - name: Copy patch archive to target nodes
ansible.builtin.copy: ansible.builtin.copy:
src: "{{ keycloak_local_path.stat.path }}/{{ keycloak_patch_bundle }}" src: "{{ local_path.stat.path }}/{{ patch_bundle }}"
dest: "{{ keycloak_patch_archive }}" dest: "{{ patch_archive }}"
owner: "{{ keycloak_service_user }}" owner: "{{ keycloak_service_user }}"
group: "{{ keycloak_service_group }}" group: "{{ keycloak_service_group }}"
mode: '0640' mode: '0640'
register: keycloak_new_version_downloaded register: new_version_downloaded
when: when:
- not keycloak_patch_archive_path.stat.exists - not patch_archive_path.stat.exists
- keycloak_local_archive_path.stat is defined - local_archive_path.stat is defined
- keycloak_local_archive_path.stat.exists - local_archive_path.stat.exists
become: "{{ keycloak_rhsso_patch_require_privilege_escalation | default(true) }}" become: "{{ keycloak_rhsso_patch_require_privilege_escalation | default(true) }}"
- name: "Check installed patches" - name: "Check installed patches"
@@ -116,14 +114,14 @@
- name: "Perform patching" - name: "Perform patching"
when: when:
- keycloak_cli_result is defined - cli_result is defined
- keycloak_cli_result.stdout is defined - cli_result.stdout is defined
- keycloak_patch_version | regex_replace('-[0-9]$', '') not in keycloak_cli_result.stdout - patch_version | regex_replace('-[0-9]$', '') not in cli_result.stdout
block: block:
- name: "Apply patch to server: {{ keycloak_patch_version }}" - name: "Apply patch {{ patch_version }} to server"
ansible.builtin.include_tasks: rhsso_cli.yml ansible.builtin.include_tasks: rhsso_cli.yml
vars: vars:
cli_query: "patch apply {{ keycloak_patch_archive }}" cli_query: "patch apply {{ patch_archive }}"
args: args:
apply: apply:
become: "{{ keycloak_rhsso_patch_require_privilege_escalation | default(true) }}" become: "{{ keycloak_rhsso_patch_require_privilege_escalation | default(true) }}"
@@ -134,15 +132,15 @@
vars: vars:
cli_query: "shutdown --restart" cli_query: "shutdown --restart"
when: when:
- keycloak_cli_result.rc == 0 - cli_result.rc == 0
args: args:
apply: apply:
become: "{{ keycloak_rhsso_patch_require_privilege_escalation | default(true) }}" become: "{{ keycloak_rhsso_patch_require_privilege_escalation | default(true) }}"
become_user: "{{ keycloak_service_user }}" become_user: "{{ keycloak_service_user }}"
- name: "Wait until service becomes active: {{ keycloak_config.service_name }}" - name: "Wait until {{ keycloak.service_name }} becomes active {{ keycloak.health_url }}"
ansible.builtin.uri: ansible.builtin.uri:
url: "{{ keycloak_config.health_url }}" url: "{{ keycloak.health_url }}"
register: keycloak_status register: keycloak_status
until: keycloak_status.status == 200 until: keycloak_status.status == 200
retries: 25 retries: 25
@@ -160,14 +158,14 @@
- name: "Verify installed patch version" - name: "Verify installed patch version"
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- keycloak_patch_version not in keycloak_cli_result.stdout - patch_version not in cli_result.stdout
fail_msg: "Patch installation failed" fail_msg: "Patch installation failed"
success_msg: "Patch installation successful" success_msg: "Patch installation successful"
- name: "Skipping patch" - name: "Skipping patch"
ansible.builtin.debug: ansible.builtin.debug:
msg: "Cumulative patch {{ keycloak_patch_version }} already installed, skipping patch installation." msg: "Cumulative patch {{ patch_version }} already installed, skipping patch installation."
when: when:
- keycloak_cli_result is defined - cli_result is defined
- keycloak_cli_result.stdout is defined - cli_result.stdout is defined
- keycloak_patch_version in keycloak_cli_result.stdout - patch_version in cli_result.stdout

View File

@@ -1,5 +1,5 @@
--- ---
- name: "Start service: {{ keycloak_config.service_name }}" - name: "Start {{ keycloak.service_name }} service"
ansible.builtin.systemd: ansible.builtin.systemd:
name: keycloak name: keycloak
enabled: true enabled: true
@@ -7,9 +7,9 @@
daemon_reload: true daemon_reload: true
become: "{{ keycloak_start_require_privilege_escalation | default(true) }}" become: "{{ keycloak_start_require_privilege_escalation | default(true) }}"
- name: "Wait until service becomes active: {{ keycloak_config.service_name }}" - name: "Wait until {{ keycloak.service_name }} becomes active {{ keycloak.health_url }}"
ansible.builtin.uri: ansible.builtin.uri:
url: "{{ keycloak_config.health_url }}" url: "{{ keycloak.health_url }}"
register: keycloak_status register: keycloak_status
until: keycloak_status.status == 200 until: keycloak_status.status == 200
retries: "{{ keycloak_service_start_retries }}" retries: "{{ keycloak_service_start_retries }}"

View File

@@ -1,5 +1,5 @@
--- ---
- name: "Stop {{ keycloak_config.service_name }}" - name: "Stop {{ keycloak.service_name }}"
ansible.builtin.systemd: ansible.builtin.systemd:
name: keycloak name: keycloak
enabled: true enabled: true

View File

@@ -1,5 +1,5 @@
--- ---
- name: "Configure service script wrapper: {{ keycloak_config.service_name }}" - name: "Configure {{ keycloak.service_name }} service script wrapper"
become: "{{ keycloak_systemd_require_privilege_escalation | default(true) }}" become: "{{ keycloak_systemd_require_privilege_escalation | default(true) }}"
ansible.builtin.template: ansible.builtin.template:
src: keycloak-service.sh.j2 src: keycloak-service.sh.j2
@@ -10,7 +10,7 @@
notify: notify:
- restart keycloak - restart keycloak
- name: "Configure sysconfig file for service: {{ keycloak_config.service_name }}" - name: "Configure sysconfig file for {{ keycloak.service_name }} service"
become: "{{ keycloak_systemd_require_privilege_escalation | default(true) }}" become: "{{ keycloak_systemd_require_privilege_escalation | default(true) }}"
ansible.builtin.template: ansible.builtin.template:
src: keycloak-sysconfig.j2 src: keycloak-sysconfig.j2
@@ -21,7 +21,7 @@
notify: notify:
- restart keycloak - restart keycloak
- name: "Configure systemd unit file for service: {{ keycloak_config.service_name }}" - name: "Configure systemd unit file for {{ keycloak.service_name }} service"
ansible.builtin.template: ansible.builtin.template:
src: keycloak.service.j2 src: keycloak.service.j2
dest: /etc/systemd/system/keycloak.service dest: /etc/systemd/system/keycloak.service
@@ -29,16 +29,16 @@
group: root group: root
mode: '0644' mode: '0644'
become: "{{ keycloak_systemd_require_privilege_escalation | default(true) }}" become: "{{ keycloak_systemd_require_privilege_escalation | default(true) }}"
register: keycloak_systemdunit register: systemdunit
notify: notify:
- restart keycloak - restart keycloak
- name: "Start and wait for service (first node db): {{ keycloak_config.service_name }}" - name: "Start and wait for {{ keycloak.service_name }} service (first node db)"
ansible.builtin.include_tasks: start_keycloak.yml ansible.builtin.include_tasks: start_keycloak.yml
run_once: true run_once: true
when: keycloak_db_enabled when: keycloak_db_enabled
- name: "Start and wait for service (remaining nodes): {{ keycloak_config.service_name }}" - name: "Start and wait for {{ keycloak.service_name }} service (remaining nodes)"
ansible.builtin.include_tasks: start_keycloak.yml ansible.builtin.include_tasks: start_keycloak.yml
- name: Check service status - name: Check service status

View File

@@ -1,3 +1,3 @@
{% for feature in keycloak_config.features %} {% for feature in keycloak.features %}
feature.{{ feature.name }}={{ feature.status | default('enabled') }} feature.{{ feature.name }}={{ feature.status | default('enabled') }}
{% endfor %} {% endfor %}

View File

@@ -17,7 +17,7 @@ checkEnvVar() {
# for testing outside systemd # for testing outside systemd
. /etc/sysconfig/keycloak . /etc/sysconfig/keycloak
readonly KEYCLOAK_HOME={{ keycloak_config.home }} readonly KEYCLOAK_HOME={{ keycloak.home }}
readonly KEYCLOAK_BIND_ADDRESS=${KEYCLOAK_BIND_ADDRESS} readonly KEYCLOAK_BIND_ADDRESS=${KEYCLOAK_BIND_ADDRESS}
readonly KEYCLOAK_HTTP_PORT=${KEYCLOAK_HTTP_PORT} readonly KEYCLOAK_HTTP_PORT=${KEYCLOAK_HTTP_PORT}
readonly KEYCLOAK_HTTPS_PORT=${KEYCLOAK_HTTPS_PORT} readonly KEYCLOAK_HTTPS_PORT=${KEYCLOAK_HTTPS_PORT}

View File

@@ -1,7 +1,7 @@
{{ ansible_managed | comment }} {{ ansible_managed | comment }}
JAVA_OPTS='{{ keycloak_java_opts }}' JAVA_OPTS='{{ keycloak_java_opts }}'
JAVA_HOME={{ keycloak_java_home | default(keycloak_pkg_java_home, true) }} JAVA_HOME={{ keycloak_java_home | default(keycloak_pkg_java_home, true) }}
JBOSS_HOME={{ keycloak_config.home }} JBOSS_HOME={{ keycloak.home }}
KEYCLOAK_BIND_ADDRESS={{ keycloak_bind_address }} KEYCLOAK_BIND_ADDRESS={{ keycloak_bind_address }}
KEYCLOAK_HTTP_PORT={{ keycloak_http_port }} KEYCLOAK_HTTP_PORT={{ keycloak_http_port }}
KEYCLOAK_HTTPS_PORT={{ keycloak_https_port }} KEYCLOAK_HTTPS_PORT={{ keycloak_https_port }}

View File

@@ -1,6 +1,6 @@
{{ ansible_managed | comment }} {{ ansible_managed | comment }}
[Unit] [Unit]
Description={{ keycloak_config.service_name }} Server Description={{ keycloak.service_name }} Server
After=network.target After=network.target
StartLimitIntervalSec={{ keycloak_service_startlimitintervalsec }} StartLimitIntervalSec={{ keycloak_service_startlimitintervalsec }}
StartLimitBurst={{ keycloak_service_startlimitburst }} StartLimitBurst={{ keycloak_service_startlimitburst }}
@@ -13,8 +13,8 @@ Group={{ keycloak_service_group }}
{% endif -%} {% endif -%}
EnvironmentFile=-{{ keycloak_sysconf_file }} EnvironmentFile=-{{ keycloak_sysconf_file }}
PIDFile={{ keycloak_service_pidfile }} PIDFile={{ keycloak_service_pidfile }}
ExecStart={{ keycloak_config.home }}/bin/standalone.sh $WILDFLY_OPTS ExecStart={{ keycloak.home }}/bin/standalone.sh $WILDFLY_OPTS
WorkingDirectory={{ keycloak_config.home }} WorkingDirectory={{ keycloak.home }}
TimeoutStartSec=30 TimeoutStartSec=30
TimeoutStopSec=30 TimeoutStopSec=30
LimitNOFILE=102642 LimitNOFILE=102642

View File

@@ -526,7 +526,7 @@
<properties> <properties>
<property name="frontendUrl" value="{{ keycloak_modcluster.frontend_url }}"/> <property name="frontendUrl" value="{{ keycloak_modcluster.frontend_url }}"/>
<property name="forceBackendUrlToFrontendUrl" value="{{ keycloak_modcluster.force_frontend_url }}"/> <property name="forceBackendUrlToFrontendUrl" value="{{ keycloak_modcluster.force_frontend_url }}"/>
{% if (keycloak_modcluster.admin_url | default('', true)) | length > 0 %} {% if keycloak_modcluster.admin_url | length > 0 %}
<property name="adminUrl" value="{{ keycloak_modcluster.admin_url }}" /> <property name="adminUrl" value="{{ keycloak_modcluster.admin_url }}" />
{% endif %} {% endif %}
</properties> </properties>
@@ -541,7 +541,7 @@
<subsystem xmlns="urn:wildfly:metrics:1.0" security-enabled="false" exposed-subsystems="*" prefix="${wildfly.metrics.prefix:jboss}"/> <subsystem xmlns="urn:wildfly:metrics:1.0" security-enabled="false" exposed-subsystems="*" prefix="${wildfly.metrics.prefix:jboss}"/>
{% if keycloak_modcluster.enabled %} {% if keycloak_modcluster.enabled %}
<subsystem xmlns="urn:jboss:domain:modcluster:5.0"> <subsystem xmlns="urn:jboss:domain:modcluster:5.0">
<proxy name="default" advertise="false" listener="ajp" proxies="{{ ['proxy_'] | product((keycloak_modcluster.reverse_proxy_urls | default([])) | map(attribute='host')) | map('join') | list | join(' ') }}"> <proxy name="default" advertise="false" listener="ajp" proxies="{{ ['proxy_'] | product(keycloak_modcluster.reverse_proxy_urls | map(attribute='host')) | map('join') | list | join(' ') }}">
<dynamic-load-provider> <dynamic-load-provider>
<load-metric type="cpu"/> <load-metric type="cpu"/>
</dynamic-load-provider> </dynamic-load-provider>

View File

@@ -1,7 +1,9 @@
--- ---
# internal variables below # internal variables below
keycloak_config:
keycloak:
home: "{{ keycloak_jboss_home }}" home: "{{ keycloak_jboss_home }}"
config_dir: "{{ keycloak_config_dir }}" config_dir: "{{ keycloak_config_dir }}"
bundle: "{{ keycloak_archive }}" bundle: "{{ keycloak_archive }}"

View File

@@ -1,9 +1,9 @@
--- ---
- name: "Invalidate theme cache: {{ keycloak.service_name }}" - name: "Invalidate {{ keycloak.service_name }} theme cache"
ansible.builtin.include_tasks: invalidate_theme_cache.yml ansible.builtin.include_tasks: invalidate_theme_cache.yml
listen: "invalidate keycloak theme cache" listen: "invalidate keycloak theme cache"
# handler should be invoked anytime a [build configuration](https://www.keycloak.org/server/all-config?f=build) changes # handler should be invoked anytime a [build configuration](https://www.keycloak.org/server/all-config?f=build) changes
- name: "Rebuild config: {{ keycloak.service_name }}" - name: "Rebuild {{ keycloak.service_name }} config"
ansible.builtin.include_tasks: rebuild_config.yml ansible.builtin.include_tasks: rebuild_config.yml
listen: "rebuild keycloak config" listen: "rebuild keycloak config"
- name: "Bootstrapped" - name: "Bootstrapped"
@@ -15,7 +15,7 @@
listen: "restart keycloak" listen: "restart keycloak"
- name: "Display deprecation warning" - name: "Display deprecation warning"
ansible.builtin.fail: ansible.builtin.fail:
msg: "Deprecation warning: you are using the deprecated variable '{{ keycloak_quarkus_deprecated_variable | d('NotSet') }}', check docs on how to upgrade." msg: "Deprecation warning: you are using the deprecated variable '{{ deprecated_variable | d('NotSet') }}', check docs on how to upgrade."
failed_when: false failed_when: false
changed_when: true changed_when: true
listen: "print deprecation warning" listen: "print deprecation warning"

View File

@@ -564,5 +564,5 @@ argument_specs:
type: "str" type: "str"
rhbk_product_category: rhbk_product_category:
default: "RHBK" default: "RHBK"
description: "Unified Downloads API category for Red Hat Build of Keycloak" description: "JBossNetwork API category for Red Hat Build of Keycloak"
type: "str" type: "str"

View File

@@ -1,7 +1,7 @@
--- ---
- name: "Initialize configuration key store variables to be written" - name: "Initialize configuration key store variables to be written"
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_quarkus_store_items: store_items:
- key: "kc.db-password" - key: "kc.db-password"
value: "{{ keycloak_quarkus_db_pass }}" value: "{{ keycloak_quarkus_db_pass }}"

View File

@@ -13,7 +13,7 @@
changed_when: keycloak_quarkus_show_deprecation_warnings changed_when: keycloak_quarkus_show_deprecation_warnings
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_quarkus_https_key_store_file: "{{ keycloak_quarkus_key_store_file }}" keycloak_quarkus_https_key_store_file: "{{ keycloak_quarkus_key_store_file }}"
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_key_store_file" # read in deprecation handler deprecated_variable: "keycloak_quarkus_key_store_file" # read in deprecation handler
notify: notify:
- print deprecation warning - print deprecation warning
@@ -28,7 +28,7 @@
changed_when: keycloak_quarkus_show_deprecation_warnings changed_when: keycloak_quarkus_show_deprecation_warnings
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_quarkus_https_key_store_password: "{{ keycloak_quarkus_key_store_password }}" keycloak_quarkus_https_key_store_password: "{{ keycloak_quarkus_key_store_password }}"
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_key_store_password" # read in deprecation handler deprecated_variable: "keycloak_quarkus_key_store_password" # read in deprecation handler
notify: notify:
- print deprecation warning - print deprecation warning
@@ -45,7 +45,7 @@
run_once: true run_once: true
changed_when: keycloak_quarkus_show_deprecation_warnings changed_when: keycloak_quarkus_show_deprecation_warnings
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_proxy_mode" # read in deprecation handler deprecated_variable: "keycloak_quarkus_proxy_mode" # read in deprecation handler
notify: notify:
- print deprecation warning - print deprecation warning
@@ -60,7 +60,7 @@
changed_when: keycloak_quarkus_show_deprecation_warnings changed_when: keycloak_quarkus_show_deprecation_warnings
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_quarkus_hostname: "{{ keycloak_quarkus_frontend_url }}" keycloak_quarkus_hostname: "{{ keycloak_quarkus_frontend_url }}"
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_frontend_url" # read in deprecation handler deprecated_variable: "keycloak_quarkus_frontend_url" # read in deprecation handler
notify: notify:
- print deprecation warning - print deprecation warning
@@ -81,7 +81,7 @@
{% set protocol = 'http://' %} {% set protocol = 'http://' %}
{% endif %} {% endif %}
{{ protocol }}{{ keycloak_quarkus_host }}:{{ keycloak_quarkus_port }}/{{ keycloak_quarkus_path }} {{ protocol }}{{ keycloak_quarkus_host }}:{{ keycloak_quarkus_port }}/{{ keycloak_quarkus_path }}
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_hostname_strict_https or keycloak_quarkus_frontend_url or keycloak_quarkus_frontend_url or keycloak_quarkus_hostname" # read in deprecation handler deprecated_variable: "keycloak_quarkus_hostname_strict_https or keycloak_quarkus_frontend_url or keycloak_quarkus_frontend_url or keycloak_quarkus_hostname" # read in deprecation handler
notify: notify:
- print deprecation warning - print deprecation warning
@@ -96,7 +96,7 @@
changed_when: keycloak_quarkus_show_deprecation_warnings changed_when: keycloak_quarkus_show_deprecation_warnings
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_quarkus_hostname_admin: "{{ keycloak_quarkus_admin_url }}" keycloak_quarkus_hostname_admin: "{{ keycloak_quarkus_admin_url }}"
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_admin_url" # read in deprecation handler deprecated_variable: "keycloak_quarkus_admin_url" # read in deprecation handler
notify: notify:
- print deprecation warning - print deprecation warning
@@ -111,7 +111,7 @@
changed_when: keycloak_quarkus_show_deprecation_warnings changed_when: keycloak_quarkus_show_deprecation_warnings
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_quarkus_hostname_backchannel_dynamic: "{{ keycloak_quarkus_hostname_strict_backchannel == False }}" keycloak_quarkus_hostname_backchannel_dynamic: "{{ keycloak_quarkus_hostname_strict_backchannel == False }}"
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_hostname_backchannel_dynamic" # read in deprecation handler deprecated_variable: "keycloak_quarkus_hostname_backchannel_dynamic" # read in deprecation handler
notify: notify:
- print deprecation warning - print deprecation warning
@@ -126,7 +126,7 @@
changed_when: keycloak_quarkus_show_deprecation_warnings changed_when: keycloak_quarkus_show_deprecation_warnings
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_quarkus_bootstrap_admin_user: "{{ keycloak_quarkus_admin_user }}" keycloak_quarkus_bootstrap_admin_user: "{{ keycloak_quarkus_admin_user }}"
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_admin_user" # read in deprecation handler deprecated_variable: "keycloak_quarkus_admin_user" # read in deprecation handler
notify: notify:
- print deprecation warning - print deprecation warning
@@ -141,7 +141,7 @@
changed_when: keycloak_quarkus_show_deprecation_warnings changed_when: keycloak_quarkus_show_deprecation_warnings
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_quarkus_bootstrap_admin_user: "{{ keycloak_quarkus_admin_pass }}" keycloak_quarkus_bootstrap_admin_user: "{{ keycloak_quarkus_admin_pass }}"
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_admin_pass" # read in deprecation handler deprecated_variable: "keycloak_quarkus_admin_pass" # read in deprecation handler
notify: notify:
- print deprecation warning - print deprecation warning
@@ -154,7 +154,7 @@
changed_when: keycloak_quarkus_show_deprecation_warnings changed_when: keycloak_quarkus_show_deprecation_warnings
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_quarkus_http_host: "{{ keycloak_quarkus_bind_address }}" keycloak_quarkus_http_host: "{{ keycloak_quarkus_bind_address }}"
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_bind_address" # read in deprecation handler deprecated_variable: "keycloak_quarkus_bind_address" # read in deprecation handler
notify: notify:
- print deprecation warning - print deprecation warning

View File

@@ -1,24 +1,24 @@
--- ---
- name: "Check if packages are already installed" # noqa command-instead-of-module this runs faster - name: "Check if packages are already installed" # noqa command-instead-of-module this runs faster
ansible.builtin.command: "rpm -q {{ packages_list | join(' ') }}" ansible.builtin.command: "rpm -q {{ packages_list | join(' ') }}"
register: keycloak_quarkus_rpm_info register: rpm_info
changed_when: false changed_when: false
failed_when: false failed_when: false
when: ansible_facts.os_family == "RedHat" when: ansible_facts.os_family == "RedHat"
- name: "Add missing packages to the yum install list" - name: "Add missing packages to the yum install list"
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_quarkus_packages_to_install: "{{ keycloak_quarkus_packages_to_install | default([]) + keycloak_quarkus_rpm_info.stdout_lines | \ packages_to_install: "{{ packages_to_install | default([]) + rpm_info.stdout_lines | \
map('regex_findall', 'package (.+) is not installed$') | default([]) | flatten }}" map('regex_findall', 'package (.+) is not installed$') | default([]) | flatten }}"
when: ansible_facts.os_family == "RedHat" when: ansible_facts.os_family == "RedHat"
- name: "Install packages: {{ keycloak_quarkus_packages_to_install }}" - name: "Install packages: {{ packages_to_install }}"
become: "{{ keycloak_quarkus_fastpackages_require_privilege_escalation | default(true) }}" become: "{{ keycloak_quarkus_fastpackages_require_privilege_escalation | default(true) }}"
ansible.builtin.dnf: ansible.builtin.dnf:
name: "{{ keycloak_quarkus_packages_to_install }}" name: "{{ packages_to_install }}"
state: present state: present
when: when:
- keycloak_quarkus_packages_to_install | default([]) | length > 0 - packages_to_install | default([]) | length > 0
- ansible_facts.os_family == "RedHat" - ansible_facts.os_family == "RedHat"
- name: "Install packages: {{ packages_list }}" - name: "Install packages: {{ packages_list }}"

View File

@@ -12,7 +12,7 @@
enabled: true enabled: true
state: started state: started
- name: "Configure firewall for http port: {{ keycloak.service_name }}" - name: "Configure firewall for {{ keycloak.service_name }} http port"
become: "{{ keycloak_quarkus_firewalld_require_privilege_escalation | default(true) }}" become: "{{ keycloak_quarkus_firewalld_require_privilege_escalation | default(true) }}"
ansible.posix.firewalld: ansible.posix.firewalld:
port: "{{ item }}" port: "{{ item }}"
@@ -23,7 +23,7 @@
- "{{ keycloak_quarkus_http_port }}/tcp" - "{{ keycloak_quarkus_http_port }}/tcp"
when: keycloak_quarkus_http_enabled | bool when: keycloak_quarkus_http_enabled | bool
- name: "Configure firewall for ports: {{ keycloak.service_name }}" - name: "Configure firewall for {{ keycloak.service_name }} ports"
become: "{{ keycloak_quarkus_firewalld_require_privilege_escalation | default(true) }}" become: "{{ keycloak_quarkus_firewalld_require_privilege_escalation | default(true) }}"
ansible.posix.firewalld: ansible.posix.firewalld:
port: "{{ item }}" port: "{{ item }}"

View File

@@ -8,25 +8,25 @@
- keycloak_quarkus_archive is defined - keycloak_quarkus_archive is defined
- keycloak_quarkus_download_url is defined - keycloak_quarkus_download_url is defined
- keycloak_quarkus_version is defined - keycloak_quarkus_version is defined
- keycloak_quarkus_local_path is defined - local_path is defined
quiet: true quiet: true
- name: Check for an existing deployment - name: Check for an existing deployment
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ keycloak.home }}" path: "{{ keycloak.home }}"
register: keycloak_quarkus_existing_deploy register: existing_deploy
- name: Stop and restart if existing deployment exists and install forced - name: Stop and restart if existing deployment exists and install forced
when: keycloak_quarkus_existing_deploy.stat.exists and keycloak_quarkus_force_install | bool when: existing_deploy.stat.exists and keycloak_quarkus_force_install | bool
block: block:
- name: "Stop the old service: {{ keycloak.service_name }}" - name: "Stop the old {{ keycloak.service_name }} service"
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
failed_when: false failed_when: false
ansible.builtin.systemd: ansible.builtin.systemd:
name: keycloak name: keycloak
state: stopped state: stopped
- name: "Remove the old deployment: {{ keycloak.service_name }}" - name: "Remove the old {{ keycloak.service_name }} deployment"
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
ansible.builtin.file: ansible.builtin.file:
path: "{{ keycloak_quarkus_home }}" path: "{{ keycloak_quarkus_home }}"
@@ -36,9 +36,9 @@
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ keycloak_quarkus_home }}" path: "{{ keycloak_quarkus_home }}"
register: keycloak_quarkus_existing_deploy register: existing_deploy
- name: "Create service user/group: {{ keycloak.service_name }}" - name: "Create {{ keycloak.service_name }} service user/group"
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
ansible.builtin.user: ansible.builtin.user:
name: "{{ keycloak.service_user }}" name: "{{ keycloak.service_user }}"
@@ -46,7 +46,7 @@
system: true system: true
create_home: false create_home: false
- name: "Create install location: {{ keycloak.service_name }}" - name: "Create {{ keycloak.service_name }} install location"
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
ansible.builtin.file: ansible.builtin.file:
dest: "{{ keycloak_quarkus_dest }}" dest: "{{ keycloak_quarkus_dest }}"
@@ -65,30 +65,29 @@
## check remote archive ## check remote archive
- name: Set download archive path - name: Set download archive path
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_quarkus_archive_full_path: "{{ keycloak_quarkus_dest }}/{{ keycloak.bundle }}" archive: "{{ keycloak_quarkus_dest }}/{{ keycloak.bundle }}"
- name: Check download archive path - name: Check download archive path
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ keycloak_quarkus_archive_full_path }}" path: "{{ archive }}"
register: keycloak_quarkus_archive_path register: archive_path
## download to controller ## download to controller
- name: Download keycloak archive - name: Download keycloak archive
ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user
url: "{{ keycloak_quarkus_download_url }}" url: "{{ keycloak_quarkus_download_url }}"
dest: "{{ keycloak_quarkus_local_path.stat.path }}/{{ keycloak.bundle }}" dest: "{{ local_path.stat.path }}/{{ keycloak.bundle }}"
mode: '0640' mode: '0640'
url_username: "{{ keycloak_quarkus_binary_download_user | default(omit) }}" url_username: "{{ keycloak_quarkus_binary_download_user | default(omit) }}"
url_password: "{{ keycloak_quarkus_binary_download_pass | default(omit) }}" url_password: "{{ keycloak_quarkus_binary_download_pass | default(omit) }}"
validate_certs: false
delegate_to: localhost delegate_to: localhost
become: false become: false
run_once: true run_once: true
when: when:
- keycloak_quarkus_archive_path is defined - archive_path is defined
- keycloak_quarkus_archive_path.stat is defined - archive_path.stat is defined
- not keycloak_quarkus_archive_path.stat.exists - not archive_path.stat.exists
- not keycloak.offline_install - not keycloak.offline_install
- not rhbk_enable is defined or not rhbk_enable - not rhbk_enable is defined or not rhbk_enable
@@ -96,9 +95,9 @@
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
when: when:
- keycloak_quarkus_archive_path is defined - archive_path is defined
- keycloak_quarkus_archive_path.stat is defined - archive_path.stat is defined
- not keycloak_quarkus_archive_path.stat.exists - not archive_path.stat.exists
- rhbk_enable is defined and rhbk_enable - rhbk_enable is defined and rhbk_enable
- not keycloak.offline_install - not keycloak.offline_install
- keycloak_quarkus_alternate_download_url is undefined - keycloak_quarkus_alternate_download_url is undefined
@@ -110,15 +109,14 @@
product_type: DISTRIBUTION product_type: DISTRIBUTION
product_version: "{{ rhbk_version }}" product_version: "{{ rhbk_version }}"
product_category: "{{ rhbk_product_category }}" product_category: "{{ rhbk_product_category }}"
validate_certs: false register: rhn_products
register: keycloak_quarkus_rhn_products
no_log: "{{ omit_rhn_output | default(true) }}" no_log: "{{ omit_rhn_output | default(true) }}"
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
- name: Determine install zipfile from search results - name: Determine install zipfile from search results
ansible.builtin.set_fact: ansible.builtin.set_fact:
keycloak_quarkus_rhn_filtered_products: "{{ keycloak_quarkus_rhn_products.results | selectattr('file_name', 'match', '[^/]*/' + rhbk_archive + '$') }}" rhn_filtered_products: "{{ rhn_products.results | selectattr('file_name', 'match', '[^/]*/' + rhbk_archive + '$') }}"
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
@@ -126,10 +124,9 @@
middleware_automation.common.product_download: # noqa risky-file-permissions delegated, uses controller host user middleware_automation.common.product_download: # noqa risky-file-permissions delegated, uses controller host user
client_id: "{{ rhn_username }}" client_id: "{{ rhn_username }}"
client_secret: "{{ rhn_password }}" client_secret: "{{ rhn_password }}"
product_id: "{{ (keycloak_quarkus_rhn_filtered_products | first).id }}" product_id: "{{ (rhn_filtered_products | first).id }}"
dest: "{{ keycloak_quarkus_local_path.stat.path }}/{{ keycloak.bundle }}" dest: "{{ local_path.stat.path }}/{{ keycloak.bundle }}"
mode: '0640' mode: '0640'
validate_certs: false
no_log: "{{ omit_rhn_output | default(true) }}" no_log: "{{ omit_rhn_output | default(true) }}"
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
@@ -140,15 +137,15 @@
run_once: true run_once: true
become: false become: false
when: when:
- keycloak_quarkus_archive_path is defined - archive_path is defined
- keycloak_quarkus_archive_path.stat is defined - archive_path.stat is defined
- not keycloak_quarkus_archive_path.stat.exists - not archive_path.stat.exists
- rhbk_enable is defined and rhbk_enable - rhbk_enable is defined and rhbk_enable
- not keycloak.offline_install - not keycloak.offline_install
- keycloak_quarkus_alternate_download_url is defined - keycloak_quarkus_alternate_download_url is defined
ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user
url: "{{ keycloak_quarkus_alternate_download_url }}" url: "{{ keycloak_quarkus_alternate_download_url }}"
dest: "{{ keycloak_quarkus_local_path.stat.path }}/{{ keycloak.bundle }}" dest: "{{ local_path.stat.path }}/{{ keycloak.bundle }}"
mode: '0640' mode: '0640'
url_username: "{{ keycloak_quarkus_download_user | default(omit) }}" url_username: "{{ keycloak_quarkus_download_user | default(omit) }}"
url_password: "{{ keycloak_quarkus_download_pass | default(omit) }}" url_password: "{{ keycloak_quarkus_download_pass | default(omit) }}"
@@ -156,8 +153,8 @@
- name: Check downloaded archive - name: Check downloaded archive
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ keycloak_quarkus_local_path.stat.path }}/{{ keycloak.bundle }}" path: "{{ local_path.stat.path }}/{{ keycloak.bundle }}"
register: keycloak_quarkus_local_archive_path register: local_archive_path
delegate_to: localhost delegate_to: localhost
become: false become: false
run_once: true run_once: true
@@ -165,35 +162,35 @@
## copy and unpack ## copy and unpack
- name: Copy archive to target nodes - name: Copy archive to target nodes
ansible.builtin.copy: ansible.builtin.copy:
src: "{{ keycloak_quarkus_local_path.stat.path }}/{{ keycloak.bundle }}" src: "{{ local_path.stat.path }}/{{ keycloak.bundle }}"
dest: "{{ keycloak_quarkus_archive_full_path }}" dest: "{{ archive }}"
owner: "{{ keycloak.service_user }}" owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}" group: "{{ keycloak.service_group }}"
mode: '0640' mode: '0640'
register: keycloak_quarkus_new_version_downloaded register: new_version_downloaded
when: when:
- not keycloak_quarkus_archive_path.stat.exists - not archive_path.stat.exists
- keycloak_quarkus_local_archive_path.stat is defined - local_archive_path.stat is defined
- keycloak_quarkus_local_archive_path.stat.exists - local_archive_path.stat.exists
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
- name: "Check target directory /bin/: {{ keycloak.home }}" - name: "Check target directory: {{ keycloak.home }}/bin/"
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ keycloak.home }}/bin/" path: "{{ keycloak.home }}/bin/"
register: keycloak_quarkus_path_to_workdir register: path_to_workdir
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
- name: "Extract Keycloak archive on target" # noqa no-handler need to run this here - name: "Extract Keycloak archive on target" # noqa no-handler need to run this here
ansible.builtin.unarchive: ansible.builtin.unarchive:
remote_src: true remote_src: true
src: "{{ keycloak_quarkus_archive_full_path }}" src: "{{ archive }}"
dest: "{{ keycloak_quarkus_dest }}" dest: "{{ keycloak_quarkus_dest }}"
creates: "{{ keycloak.home }}/bin/" creates: "{{ keycloak.home }}/bin/"
owner: "{{ keycloak.service_user }}" owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}" group: "{{ keycloak.service_group }}"
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}" become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
when: when:
- (not keycloak_quarkus_path_to_workdir.stat.exists) or keycloak_quarkus_new_version_downloaded.changed - (not path_to_workdir.stat.exists) or new_version_downloaded.changed
notify: notify:
- restart keycloak - restart keycloak
@@ -201,7 +198,7 @@
ansible.builtin.debug: ansible.builtin.debug:
msg: "{{ keycloak.home }} already exists and version unchanged, skipping decompression" msg: "{{ keycloak.home }} already exists and version unchanged, skipping decompression"
when: when:
- (not keycloak_quarkus_new_version_downloaded.changed) and keycloak_quarkus_path_to_workdir.stat.exists - (not new_version_downloaded.changed) and path_to_workdir.stat.exists
- name: "Copy private key to target" - name: "Copy private key to target"
ansible.builtin.copy: ansible.builtin.copy:
@@ -229,7 +226,7 @@
- keycloak_quarkus_cert_file_copy_enabled is defined and keycloak_quarkus_cert_file_copy_enabled - keycloak_quarkus_cert_file_copy_enabled is defined and keycloak_quarkus_cert_file_copy_enabled
- keycloak_quarkus_cert_file_src | length > 0 - keycloak_quarkus_cert_file_src | length > 0
- name: "Install JDBC driver: {{ keycloak_quarkus_db_engine }}" - name: "Install {{ keycloak_quarkus_db_engine }} JDBC driver"
ansible.builtin.include_tasks: jdbc_driver.yml ansible.builtin.include_tasks: jdbc_driver.yml
when: when:
- rhbk_enable is defined and rhbk_enable - rhbk_enable is defined and rhbk_enable
@@ -257,7 +254,7 @@
version: "{{ item.maven.version | default(omit) }}" version: "{{ item.maven.version | default(omit) }}"
username: "{{ item.maven.username | default(omit) }}" username: "{{ item.maven.username | default(omit) }}"
password: "{{ item.maven.password | default(omit) }}" password: "{{ item.maven.password | default(omit) }}"
dest: "{{ keycloak_quarkus_local_path.stat.path }}/{{ item.id }}.jar" dest: "{{ local_path.stat.path }}/{{ item.id }}.jar"
delegate_to: "localhost" delegate_to: "localhost"
run_once: true run_once: true
loop: "{{ keycloak_quarkus_providers }}" loop: "{{ keycloak_quarkus_providers }}"
@@ -266,7 +263,7 @@
- name: "Copy maven providers" - name: "Copy maven providers"
ansible.builtin.copy: ansible.builtin.copy:
src: "{{ keycloak_quarkus_local_path.stat.path }}/{{ item.id }}.jar" src: "{{ local_path.stat.path }}/{{ item.id }}.jar"
dest: "{{ keycloak.home }}/providers/{{ item.id }}.jar" dest: "{{ keycloak.home }}/providers/{{ item.id }}.jar"
owner: "{{ keycloak.service_user }}" owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}" group: "{{ keycloak.service_group }}"

View File

@@ -4,7 +4,7 @@
# you can do so by deleting the data/tmp/kc-gzip-cache directory of the server distribution # you can do so by deleting the data/tmp/kc-gzip-cache directory of the server distribution
# It can be useful for instance if you redeployed custom providers or custom themes without # It can be useful for instance if you redeployed custom providers or custom themes without
# disabling themes caching in the previous server executions. # disabling themes caching in the previous server executions.
- name: "Delete theme cache directory: {{ keycloak.service_name }}" - name: "Delete {{ keycloak.service_name }} theme cache directory"
ansible.builtin.file: ansible.builtin.file:
path: "{{ keycloak.home }}/data/tmp/kc-gzip-cache" path: "{{ keycloak.home }}/data/tmp/kc-gzip-cache"
state: absent state: absent

View File

@@ -123,13 +123,13 @@
- name: Check service status - name: Check service status
ansible.builtin.systemd_service: ansible.builtin.systemd_service:
name: "{{ keycloak.service_name }}" name: "{{ keycloak.service_name }}"
register: keycloak_quarkus_service_status register: keycloak_service_status
changed_when: false changed_when: false
- name: "Notify to remove `keycloak_quarkus_bootstrap_admin_user[_password]` env vars" - name: "Notify to remove `keycloak_quarkus_bootstrap_admin_user[_password]` env vars"
when: when:
- not ansible_local.keycloak.general.bootstrapped | default(false) | bool # it was not bootstrapped prior to the current role's execution - not ansible_local.keycloak.general.bootstrapped | default(false) | bool # it was not bootstrapped prior to the current role's execution
- keycloak_quarkus_service_status.status.ActiveState == "active" # but it is now - keycloak_service_status.status.ActiveState == "active" # but it is now
ansible.builtin.assert: { that: true, quiet: true } ansible.builtin.assert: { that: true, quiet: true }
changed_when: true changed_when: true
notify: notify:

View File

@@ -2,8 +2,6 @@
- name: Validate admin console password - name: Validate admin console password
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- keycloak_quarkus_bootstrap_admin_password is defined
- keycloak_quarkus_bootstrap_admin_password is not none
- keycloak_quarkus_bootstrap_admin_password | length > 12 - keycloak_quarkus_bootstrap_admin_password | length > 12
quiet: true quiet: true
fail_msg: "The console administrator password is empty or invalid. Please set the keycloak_quarkus_bootstrap_admin_password to a 12+ char long string" fail_msg: "The console administrator password is empty or invalid. Please set the keycloak_quarkus_bootstrap_admin_password to a 12+ char long string"
@@ -57,7 +55,7 @@
- name: Check local download archive path - name: Check local download archive path
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ keycloak_quarkus_download_path }}" path: "{{ keycloak_quarkus_download_path }}"
register: keycloak_quarkus_local_path register: local_path
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
become: false become: false
@@ -65,9 +63,9 @@
- name: Validate local download path - name: Validate local download path
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- keycloak_quarkus_local_path.stat.exists - local_path.stat.exists
- keycloak_quarkus_local_path.stat.readable - local_path.stat.readable
- keycloak_quarkus_offline_install or keycloak_quarkus_local_path.stat.writeable - keycloak_quarkus_offline_install or local_path.stat.writeable
quiet: true quiet: true
fail_msg: "Defined controller path for downloading resources is incorrect or unreadable: {{ keycloak_quarkus_download_path }}" fail_msg: "Defined controller path for downloading resources is incorrect or unreadable: {{ keycloak_quarkus_download_path }}"
success_msg: "Will download resource to controller path: {{ keycloak_quarkus_download_path }}" success_msg: "Will download resource to controller path: {{ keycloak_quarkus_download_path }}"
@@ -76,20 +74,20 @@
- name: Check downloaded archive if offline - name: Check downloaded archive if offline
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ keycloak_quarkus_local_path.stat.path }}/{{ keycloak.bundle }}" path: "{{ local_path.stat.path }}/{{ keycloak.bundle }}"
when: keycloak_quarkus_offline_install when: keycloak_quarkus_offline_install
register: keycloak_quarkus_local_archive_path_check register: local_archive_path_check
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
- name: Validate local downloaded archive if offline - name: Validate local downloaded archive if offline
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- keycloak_quarkus_local_archive_path_check.stat.exists - local_archive_path_check.stat.exists
- keycloak_quarkus_local_archive_path_check.stat.readable - local_archive_path_check.stat.readable
quiet: true quiet: true
fail_msg: "Configured for offline install but install archive not found at: {{ keycloak_quarkus_local_path.stat.path }}/{{ keycloak.bundle }}" fail_msg: "Configured for offline install but install archive not found at: {{ local_path.stat.path }}/{{ keycloak.bundle }}"
success_msg: "Will install offline with expected archive: {{ keycloak_quarkus_local_path.stat.path }}/{{ keycloak.bundle }}" success_msg: "Will install offline with expected archive: {{ local_path.stat.path }}/{{ keycloak.bundle }}"
when: keycloak_quarkus_offline_install when: keycloak_quarkus_offline_install
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
@@ -100,11 +98,11 @@
- name: "Check run keytool" - name: "Check run keytool"
changed_when: false changed_when: false
ansible.builtin.command: keytool -help ansible.builtin.command: keytool -help
register: keycloak_quarkus_keytool_check register: keytool_check
ignore_errors: true ignore_errors: true
- name: "Fail when no keytool found" - name: "Fail when no keytool found"
when: keycloak_quarkus_keytool_check.rc != 0 when: keytool_check.rc != 0
ansible.builtin.fail: ansible.builtin.fail:
msg: "keytool NOT found in the PATH, but is required for setting up the configuration key store" msg: "keytool NOT found in the PATH, but is required for setting up the configuration key store"

View File

@@ -1,6 +1,6 @@
--- ---
# cf. https://www.keycloak.org/server/configuration#_optimize_the_keycloak_startup # cf. https://www.keycloak.org/server/configuration#_optimize_the_keycloak_startup
- name: "Rebuild config: {{ keycloak.service_name }}" - name: "Rebuild {{ keycloak.service_name }} config"
ansible.builtin.shell: | # noqa blocked_modules shell is necessary here ansible.builtin.shell: | # noqa blocked_modules shell is necessary here
env -i bash -c "set -a ; source {{ keycloak_quarkus_sysconf_file }} ; {{ keycloak.home }}/bin/kc.sh build " env -i bash -c "set -a ; source {{ keycloak_quarkus_sysconf_file }} ; {{ keycloak.home }}/bin/kc.sh build "
become: "{{ keycloak_quarkus_rebuild_config_require_privilege_escalation | default(true) }}" become: "{{ keycloak_quarkus_rebuild_config_require_privilege_escalation | default(true) }}"

View File

@@ -1,5 +1,5 @@
--- ---
- name: "Restart and enable service: {{ keycloak.service_name }}" - name: "Restart and enable {{ keycloak.service_name }} service"
ansible.builtin.systemd: ansible.builtin.systemd:
name: "{{ keycloak.service_name }}" name: "{{ keycloak.service_name }}"
enabled: true enabled: true
@@ -7,11 +7,11 @@
daemon_reload: true daemon_reload: true
become: "{{ keycloak_quarkus_restart_require_privilege_escalation | default(true) }}" become: "{{ keycloak_quarkus_restart_require_privilege_escalation | default(true) }}"
- name: "Wait until service becomes active: {{ keycloak.service_name }}" - name: "Wait until {{ keycloak.service_name }} service becomes active {{ keycloak.health_url }}"
ansible.builtin.uri: ansible.builtin.uri:
url: "{{ keycloak.health_url }}" url: "{{ keycloak.health_url }}"
register: keycloak_quarkus_status register: keycloak_status
until: keycloak_quarkus_status.status == 200 until: keycloak_status.status == 200
retries: "{{ keycloak_quarkus_restart_health_check_retries }}" retries: "{{ keycloak_quarkus_restart_health_check_retries }}"
delay: "{{ keycloak_quarkus_restart_health_check_delay }}" delay: "{{ keycloak_quarkus_restart_health_check_delay }}"
when: internal_force_health_check | default(keycloak_quarkus_restart_health_check) when: internal_force_health_check | default(keycloak_quarkus_restart_health_check)

Some files were not shown because too many files have changed in this diff Show More