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