diff --git a/roles/web-app-xwiki/tasks/04_extensions.yml b/roles/web-app-xwiki/tasks/04_extensions.yml
index 45f9aeb5..e155e4c8 100644
--- a/roles/web-app-xwiki/tasks/04_extensions.yml
+++ b/roles/web-app-xwiki/tasks/04_extensions.yml
@@ -1,152 +1,135 @@
# roles/web-app-xwiki/tasks/04_extensions.yml
#
-# Canonical install flow for XWiki 16.10.x in this setup:
-# - PUT /rest/jobs with query params (install, async=false, media=json, namespaces, extensions)
-# - Pass lists via …
in query parameters
-# - Minimal XML body () just to satisfy the REST layer
-# - Restart once (handler: docker compose restart), then verify via the 'installed' repository
+# Installs OIDC / LDAP using a temporary Groovy page that calls the
+# Extension Script Service (services.extension.install).
+# Avoids REST job API and any Namespace class import for portability.
#
-# Requires in xwiki.properties:
-# extension.repositories=xwiki-public:maven:https://nexus.xwiki.org/nexus/content/groups/public/,central:maven:https://repo1.maven.org/maven2/
+# Flow:
+# - Bootstrap config renders with both auth backends OFF (already in your role).
+# - This file installs required extensions on the current wiki.
+# - Final config later turns auth ON (already in your role).
+#
+# Notes:
+# - We print machine-readable markers so Ansible can assert deterministically.
+# - We protect XWiki's {{groovy}} wiki macro from Jinja by using {% raw %}…{% endraw %}.
-# 1) Probe what is already installed
-- name: "Probe OIDC extension"
- include_tasks: _probe_extension.yml
- vars:
- ext_id: "{{ XWIKI_EXT_OIDC_ID }}"
- ext_version: "{{ XWIKI_EXT_OIDC_VERSION }}"
- ext_enabled: "{{ XWIKI_OIDC_ENABLED }}"
- result_var: "xwiki_oidc_ext"
-
-- name: "Probe LDAP extension"
- include_tasks: _probe_extension.yml
- vars:
- ext_id: "{{ XWIKI_EXT_LDAP_ID }}"
- ext_version: "{{ XWIKI_EXT_LDAP_VERSION }}"
- ext_enabled: "{{ XWIKI_LDAP_ENABLED }}"
- result_var: "xwiki_ldap_ext"
-
-# 2) Build the list of extensions to install (enabled + missing)
-- name: "Initialize extension install list"
+- name: "XWIKI | Build Groovy installer code (no wiki macro delimiters here)"
set_fact:
- extensions_to_install: []
+ _install_code: |
+ def ext = services.extension
+ def ns = "wiki:xwiki"
-- name: "Queue OIDC extension for install"
- when:
- - XWIKI_OIDC_ENABLED | bool
- - xwiki_oidc_ext.status | int != 200
- set_fact:
- extensions_to_install: "{{ extensions_to_install + [ {'id': XWIKI_EXT_OIDC_ID, 'version': XWIKI_EXT_OIDC_VERSION} ] }}"
+ // Build the wish list from Ansible vars
+ def wanted = []
+ {% if XWIKI_OIDC_ENABLED | bool %}
+ wanted << [id: "{{ XWIKI_EXT_OIDC_ID }}", version: "{{ XWIKI_EXT_OIDC_VERSION }}"]
+ {% endif %}
+ {% if XWIKI_LDAP_ENABLED | bool %}
+ wanted << [id: "{{ XWIKI_EXT_LDAP_ID }}", version: "{{ XWIKI_EXT_LDAP_VERSION }}"]
+ {% endif %}
-- name: "Queue LDAP extension for install"
- when:
- - XWIKI_LDAP_ENABLED | bool
- - xwiki_ldap_ext is defined
- - xwiki_ldap_ext.status | int != 200
- set_fact:
- extensions_to_install: "{{ extensions_to_install + [ {'id': XWIKI_EXT_LDAP_ID, 'version': XWIKI_EXT_LDAP_VERSION} ] }}"
+ if (wanted.isEmpty()) {
+ println "SKIP: no extensions requested"
+ } else {
+ wanted.each { e ->
+ def already = ext.getInstalledExtension(e.id as String, ns)
+ if (already) {
+ println "ALREADY_INSTALLED::${e.id}::${already.id?.version}"
+ } else {
+ println "INSTALL_START::${e.id}::${e.version}"
+ def job = ext.install(e.id as String, e.version as String, ns)
-# 3) Build XML payloads for the request BODY (not query params)
-- name: "Build XML payloads for job body"
- when: extensions_to_install | length > 0
- set_fact:
- namespaces_xml_rest: "wiki:xwiki
"
- extensions_xml_rest: >-
- {% for ext in extensions_to_install -%}
- {{ ext.id }}{{ ext.version }}wiki:xwiki
- {%- endfor %}
+ // Heartbeat until terminal state
+ long last = System.currentTimeMillis()
+ while (true) {
+ def st = job?.status?.state
+ if (st && st.name() in ["FINISHED","FAILED","CANCELED"]) {
+ println "STATE=${st.name()}::${e.id}"
+ break
+ }
+ if (System.currentTimeMillis() - last > 2000) {
+ println "STATE=" + (st?.name() ?: "PENDING") + "::" + e.id
+ last = System.currentTimeMillis()
+ }
+ Thread.sleep(500)
+ }
-# 4) Install extensions synchronously using BODY properties
-- name: "Install extensions via PUT (body properties, sync)"
- when: extensions_to_install | length > 0
+ // Verify presence after job completion
+ def now = ext.getInstalledExtension(e.id as String, ns)
+ if (now) {
+ println "INSTALLED_OK::${e.id}::${now.id?.version}"
+ } else {
+ println "INSTALLED_MISSING::${e.id}"
+ }
+ }
+ }
+ }
+
+- name: "XWIKI | PUT installer page Main.InstallExtensions"
uri:
- url: "{{ XWIKI_REST_EXTENSION_INSTALL }}?jobType=install&async=false&media=json"
+ url: "{{ [XWIKI_REST_XWIKI_PAGES, 'InstallExtensions'] | url_join }}"
method: PUT
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
+ status_code: [200, 201, 202, 204]
headers:
- Accept: "application/json"
Content-Type: "application/xml"
+ Accept: "application/xml"
body: |
-
-
-
-
- namespaces
-
-
-
- extensions
-
-
-
- interactive
- false
-
-
-
-
- status_code: [200, 201, 202]
- return_content: yes
- register: xwiki_install_job
- notify: docker compose restart
- changed_when: xwiki_install_job.json.state == 'FINISHED'
+
+ InstallExtensions
+
+ xwiki/2.1
+
+ register: _put_page
-# 5) Restart once so new classes are loaded
-- name: "Restart XWiki after install"
- when: extensions_to_install | length > 0
- meta: flush_handlers
-
-# 6) Wait for REST to be ready again
-- name: "Wait until XWiki REST is ready after restart"
+- name: "XWIKI | Execute installer page"
uri:
- url: "{{ XWIKI_REST_BASE }}"
- status_code: [200, 401]
- follow_redirects: none
- return_content: no
- register: xwiki_rest_up_after
- changed_when: false
- retries: 60
- delay: 5
- until:
- - xwiki_rest_up_after is succeeded
- - (xwiki_rest_up_after.redirected is not defined) or (not xwiki_rest_up_after.redirected)
+ url: "{{ 'http://127.0.0.1:' ~ XWIKI_HOST_PORT ~ '/bin/view/XWiki/InstallExtensions?xpage=plain' }}"
+ method: GET
+ user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
+ password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
+ force_basic_auth: true
+ status_code: [200]
+ return_content: yes
+ register: _exec_page
-# 7) Re-probe from 'installed' repository
-- name: "Re-probe OIDC extension (installed repo)"
- include_tasks: _probe_extension.yml
+# Assert success:
+# - If nothing was requested, allow "SKIP: no extensions requested".
+# - For requested OIDC/LDAP, require ALREADY_INSTALLED or INSTALLED_OK.
+# - Disallow INSTALLED_MISSING.
+- name: "ASSERT | Extension installation markers"
vars:
- ext_id: "{{ XWIKI_EXT_OIDC_ID }}"
- ext_version: "{{ XWIKI_EXT_OIDC_VERSION }}"
- ext_enabled: "{{ XWIKI_OIDC_ENABLED }}"
- result_var: "xwiki_oidc_ext_after"
+ _c: "{{ _exec_page.content | default('') }}"
+ _need_oidc: "{{ XWIKI_OIDC_ENABLED | bool }}"
+ _need_ldap: "{{ XWIKI_LDAP_ENABLED | bool }}"
+ _ok_oidc: "{{ (_c is search('ALREADY_INSTALLED::' ~ XWIKI_EXT_OIDC_ID)) or (_c is search('INSTALLED_OK::' ~ XWIKI_EXT_OIDC_ID)) }}"
+ _ok_ldap: "{{ (_c is search('ALREADY_INSTALLED::' ~ XWIKI_EXT_LDAP_ID)) or (_c is search('INSTALLED_OK::' ~ XWIKI_EXT_LDAP_ID)) }}"
+ _miss_any: "{{ _c is search('INSTALLED_MISSING::') }}"
+ _skip_all: "{{ _c is search('SKIP: no extensions requested') }}"
+ assert:
+ that:
+ - _miss_any | bool == false
+ - (_need_oidc and _ok_oidc) or (not _need_oidc)
+ - (_need_ldap and _ok_ldap) or (not _need_ldap)
+ - (_need_oidc or _need_ldap) or _skip_all
+ fail_msg: >-
+ Extension install did not complete successfully.
+ Output was:
+ {{ (_exec_page.content | default('') | regex_replace('\\s+', ' ') | truncate(1000)) }}
-- name: "Re-probe LDAP extension (installed repo)"
- include_tasks: _probe_extension.yml
- vars:
- ext_id: "{{ XWIKI_EXT_LDAP_ID }}"
- ext_version: "{{ XWIKI_EXT_LDAP_VERSION }}"
- ext_enabled: "{{ XWIKI_LDAP_ENABLED }}"
- result_var: "xwiki_ldap_ext_after"
-
-# 8) Fail fast if something requested is still missing
-- name: "FAIL: OIDC missing after install"
- fail:
- msg: >-
- OIDC extension ({{ XWIKI_EXT_OIDC_ID }} {{ XWIKI_EXT_OIDC_VERSION }}) is NOT detected
- after install (HTTP {{ xwiki_oidc_ext_after.status | default('n/a') }}).
- Check 'extension.repositories' in xwiki.properties and network/DNS.
- when:
- - XWIKI_OIDC_ENABLED | bool
- - xwiki_oidc_ext_after.status | int != 200
-
-- name: "FAIL: LDAP missing after install"
- fail:
- msg: >-
- LDAP extension ({{ XWIKI_EXT_LDAP_ID }} {{ XWIKI_EXT_LDAP_VERSION }}) is NOT detected
- after install (HTTP {{ xwiki_ldap_ext_after.status | default('n/a') }}).
- Check 'extension.repositories' in xwiki.properties and network/DNS.
- when:
- - XWIKI_LDAP_ENABLED | bool
- - xwiki_ldap_ext_after.status | int != 200
+- name: "XWIKI | Delete installer page"
+ uri:
+ url: "{{ [XWIKI_REST_XWIKI_PAGES, 'InstallExtensions'] | url_join }}"
+ method: DELETE
+ user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
+ password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
+ force_basic_auth: true
+ status_code: [204, 200, 202, 404]
+ register: _delete_page
+ changed_when: _delete_page.status != 404