web-app-xwiki: verify extensions via Groovy page + new filter

- Added new filter 'xwiki_extension_status' (strips HTML, handles  ) -> returns 200/404
- Introduced checker tasks (_check_extension_via_groovy.yml) instead of REST probe
- Added early assert: superadmin login before extension installation
- Collect and assert probe results in 04_extensions.yml
- Set OIDC extension version to 'latest' (empty string)

https://chatgpt.com/share/68ca36cb-ac38-800f-8281-8dea480b6676
This commit is contained in:
2025-09-17 06:20:28 +02:00
parent e94aac1d78
commit c64ac0b4dc
6 changed files with 139 additions and 46 deletions

View File

@@ -45,7 +45,7 @@ plugins:
enabled: true enabled: true
items: items:
- id: "org.xwiki.contrib.oidc:oidc-authenticator" - id: "org.xwiki.contrib.oidc:oidc-authenticator"
version: "2.19.2" version: ""
ldap: ldap:
enabled: false enabled: false

View File

@@ -74,9 +74,35 @@ def xwiki_job_id(response: Any, default: Optional[str] = None, strict: bool = Fa
return default return default
def xwiki_extension_status(raw: str) -> int:
"""
Parse the output of the Groovy CheckExtension page.
- Strips HTML tags and entities ( )
- Returns 200 if extension is INSTALLED, otherwise 404
Args:
raw: Raw HTTP body from the checker page.
Returns:
200 if installed, 404 if missing/unknown.
"""
if raw is None:
return 404
text = re.sub(r"<[^>]+>", "", str(raw))
text = text.replace("&nbsp;", " ").replace("\u00A0", " ")
text = text.strip()
if text.startswith("INSTALLED::"):
return 200
return 404
class FilterModule(object): class FilterModule(object):
"""Custom filters for XWiki helpers.""" """Custom filters for XWiki helpers."""
def filters(self): def filters(self):
return { return {
"xwiki_job_id": xwiki_job_id, "xwiki_job_id": xwiki_job_id,
"xwiki_extension_status": xwiki_extension_status,
} }

View File

@@ -15,6 +15,21 @@
xwiki_ldap_enabled_switch: false xwiki_ldap_enabled_switch: false
xwiki_superadmin_enabled_switch: true xwiki_superadmin_enabled_switch: true
- name: "ASSERT | superadmin can authenticate (needed for installer)"
uri:
url: "{{ [XWIKI_REST_XWIKI, 'spaces'] | url_join }}"
method: GET
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
status_code: [200]
register: _super_check_ext
- name: "FAIL | superadmin authentication failed (extensions phase)"
fail:
msg: "superadmin authentication failed (check xwiki.cfg in image / password / Dockerfile build)"
when: _super_check_ext.status != 200
- name: Load setup procedures for admin - name: Load setup procedures for admin
include_tasks: 03_administrator.yml include_tasks: 03_administrator.yml
when: not (XWIKI_SSO_ENABLED | bool) when: not (XWIKI_SSO_ENABLED | bool)

View File

@@ -57,6 +57,39 @@
delay: 15 delay: 15
until: _exec_page is succeeded until: _exec_page is succeeded
- name: "XWIKI | Verify requested extensions via Groovy checker"
include_tasks: _check_extension_via_groovy.yml
loop: "{{ XWIKI_PLUGINS }}"
loop_control:
loop_var: plugin
label: "{{ plugin.id }}"
vars:
ext_id: "{{ plugin.id }}"
result_var: "probe_{{ plugin.id | regex_replace('[^A-Za-z0-9_]', '_') }}"
- name: "XWIKI | Collect probe results"
set_fact:
_xwiki_probe_results: "{{ _xwiki_probe_results | default([]) + [ {
'id': plugin.id,
'status': (
(hostvars[inventory_hostname]['probe_' ~ (plugin.id | regex_replace('[^A-Za-z0-9_]', '_'))]
| default({})).status
| default(404) | int
)
} ] }}"
loop: "{{ XWIKI_PLUGINS }}"
loop_control:
loop_var: plugin
changed_when: false
# Fail if any extension is missing
- name: "XWIKI | Assert all requested extensions are installed"
vars:
missing: "{{ _xwiki_probe_results | selectattr('status','equalto',404) | map(attribute='id') | list }}"
fail:
msg: "Missing extensions: {{ missing | join(', ') }}"
when: missing | length > 0
- name: "XWIKI | Delete installer page" - name: "XWIKI | Delete installer page"
uri: uri:
url: "{{ [XWIKI_REST_XWIKI_PAGES, 'InstallExtensions'] | url_join }}" url: "{{ [XWIKI_REST_XWIKI_PAGES, 'InstallExtensions'] | url_join }}"

View File

@@ -0,0 +1,63 @@
# PUT a temporary Groovy page that checks installed extensions
- name: "XWIKI | PUT checker page XWiki.CheckExtension"
uri:
url: "{{ [XWIKI_REST_XWIKI_PAGES, 'CheckExtension'] | url_join }}"
method: PUT
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
status_code: [200, 201, 202, 204]
headers:
Content-Type: "application/xml"
Accept: "application/xml"
body: |
<page xmlns="http://www.xwiki.org">
<title>CheckExtension</title>
<content><![CDATA[
{% raw %}{{groovy}}{% endraw %}
def ns = "wiki:xwiki"
def id = (request.getParameter("id") ?: "").toString()
if (!id) { print "ERROR::missing-id"; return }
def ext = services.extension.getInstalledExtension(id, ns)
if (ext) {
print "INSTALLED::${ext.id?.id}::${ext.id?.version}"
} else {
print "MISSING::${id}"
}
{% raw %}{{/groovy}}{% endraw %}
]]></content>
<syntax>xwiki/2.1</syntax>
</page>
register: _put_checker
changed_when: false
# Call the page to check a single extension
- name: "XWIKI | Check installed via Groovy for {{ ext_id }}"
uri:
url: "http://127.0.0.1:{{ XWIKI_HOST_PORT }}/bin/view/XWiki/CheckExtension?xpage=plain&id={{ ext_id | urlencode }}"
method: GET
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
status_code: [200]
return_content: yes
register: _check_output
changed_when: false
- name: "XWIKI | Save Groovy check result for {{ ext_id }}"
set_fact:
"{{ result_var }}":
status: "{{ _check_output.content | xwiki_extension_status }}"
raw: "{{ _check_output.content }}"
# Cleanup (optional; you can leave the page, but we remove it to keep things tidy)
- name: "XWIKI | Delete checker page"
uri:
url: "{{ [XWIKI_REST_XWIKI_PAGES, 'CheckExtension'] | url_join }}"
method: DELETE
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
status_code: [204, 200, 202, 404]
register: _delete_checker
changed_when: _delete_checker.status != 404

View File

@@ -1,44 +0,0 @@
# roles/web-app-xwiki/tasks/_probe_extension.yml
# Probes the 'installed' extension repository to check if a given extension is present.
# Uses the wiki-namespaced REST base (/rest/wikis/xwiki) and falls back to /{id}/{version} if needed.
- name: "XWIKI | Probe extension {{ ext_id }} (installed repo)"
when: ext_enabled | bool
uri:
url: "{{ [XWIKI_REST_XWIKI, 'repositories/installed/extensions', ext_id | urlencode] | url_join }}?namespace={{ 'wiki:xwiki' | urlencode }}"
method: GET
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
follow_redirects: none
return_content: no
headers:
Accept: "application/xml"
status_code: [200, 401, 404]
register: _probe
changed_when: false
# Some XWiki builds/versions answer on /{id}/{version}. Try that if plain /{id} returned 404.
- name: "XWIKI | Probe extension {{ ext_id }} with version (fallback)"
when:
- ext_enabled | bool
- (_probe.status | default(404)) | int == 404
- ext_version is defined
uri:
url: "{{ [XWIKI_REST_XWIKI, 'repositories/installed/extensions', ext_id | urlencode, ext_version] | url_join }}?namespace={{ 'wiki:xwiki' | urlencode }}"
method: GET
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: true
follow_redirects: none
return_content: no
headers:
Accept: "application/xml"
status_code: [200, 401, 404]
register: _probe_v
changed_when: false
- name: "XWIKI | Save probe result for {{ ext_id }}"
when: ext_enabled | bool
set_fact:
"{{ result_var }}": "{{ (_probe_v if (_probe_v is defined) else _probe) }}"