xwiki: install/verify via REST Job API; add 'xwiki_job_id' filter; refactor extension probe; remove invalid /extensions/{id} verify; README wording

Context: fixed 404 on 'Verify OIDC extension is installed' by polling jobstatus and parsing job id via filter plugin.
Conversation: https://chatgpt.com/share/68c435b7-96c0-800f-b7d6-b3fe99b443e0
This commit is contained in:
2025-09-12 17:01:37 +02:00
parent d7d8578b13
commit 2f992983f4
4 changed files with 145 additions and 36 deletions

View File

@@ -6,7 +6,7 @@ Empower your organization with **XWiki**, an open-source enterprise wiki and kno
## Overview ## Overview
This role deploys XWiki using Docker, automating the installation, configuration, and management of your XWiki server. It integrates with an MariaDB database and an Nginx reverse proxy. The role supports advanced features such as global CSS injection, Matomo analytics, OIDC authentication, and centralized logout, making it a powerful and customizable solution within the Infinito.Nexus ecosystem. This role deploys XWiki using Docker, automating the installation, configuration, and management of your XWiki server. It integrates with an relational database and a reverse proxy. The role supports advanced features such as global CSS injection, Matomo analytics, OIDC authentication, and centralized logout, making it a powerful and customizable solution within the Infinito.Nexus ecosystem.
## Features ## Features

View File

@@ -0,0 +1,82 @@
# filter_plugins/xwiki_filters.py
from __future__ import annotations
import re
from typing import Any, Dict, Optional, Iterable
try:
# Ansible provides this; don't hard-depend at import time for unit tests
from ansible.errors import AnsibleFilterError
except Exception: # pragma: no cover
class AnsibleFilterError(Exception):
pass
_JOB_LOC_RE = re.compile(r"/rest/jobstatus/([^?\s#]+)")
def _join_elements(elems: Iterable[Any]) -> str:
return "/".join(str(x) for x in elems)
def xwiki_job_id(response: Any, default: Optional[str] = None, strict: bool = False) -> Optional[str]:
"""
Extract a XWiki job ID from a typical Ansible `uri` response.
Supports:
- JSON mapping: {"id": {"elements": ["install", "extensions", "123"]}}
- JSON mapping: {"id": "install/extensions/123"}
- Fallback from Location header or URL containing "/rest/jobstatus/<id>"
Args:
response: The registered result from the `uri` task (dict-like).
default: Value to return when no ID can be found (if strict=False).
strict: If True, raise AnsibleFilterError when no ID is found.
Returns:
The job ID string, or `default`/None.
Raises:
AnsibleFilterError: if `strict=True` and no job ID can be determined.
"""
if not isinstance(response, dict):
if strict:
raise AnsibleFilterError("xwiki_job_id: response must be a dict-like Ansible result.")
return default
# 1) Try JSON body
j = response.get("json")
if isinstance(j, dict):
job_id = j.get("id")
if isinstance(job_id, dict):
elems = job_id.get("elements")
if isinstance(elems, list) and elems:
return _join_elements(elems)
if isinstance(job_id, str) and job_id.strip():
return job_id.strip()
# 2) Fallback: Location header (Ansible `uri` exposes it as `location`)
loc = response.get("location")
if isinstance(loc, str) and loc:
m = _JOB_LOC_RE.search(loc)
if m:
return m.group(1)
# 3) As a last resort, try the final `url` (in case server redirected and Ansible captured it)
url = response.get("url")
if isinstance(url, str) and url:
m = _JOB_LOC_RE.search(url)
if m:
return m.group(1)
# Not found
if strict:
raise AnsibleFilterError("xwiki_job_id: could not extract job ID from response.")
return default
class FilterModule(object):
"""Custom filters for XWiki helpers."""
def filters(self):
return {
"xwiki_job_id": xwiki_job_id,
}

View File

@@ -1,55 +1,60 @@
- name: "Check if OIDC extension installed" - name: "Probe OIDC extension"
uri: include_tasks: _probe_extension.yml
url: "{{ XWIKI_REST_XWIKI }}/extensions/{{ XWIKI_EXT_OIDC_ID | urlencode }}" vars:
method: GET ext_id: "{{ XWIKI_EXT_OIDC_ID }}"
user: "{{ XWIKI_SUPERADMIN_USERNAME }}" ext_enabled: "{{ XWIKI_OIDC_ENABLED }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}" result_var: "xwiki_oidc_ext"
force_basic_auth: yes
status_code: [200, 404, 302]
register: xwiki_oidc_ext
when: XWIKI_OIDC_ENABLED | bool
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "Check if LDAP extension installed" - name: "Probe LDAP extension"
uri: include_tasks: _probe_extension.yml
url: "{{ XWIKI_REST_XWIKI }}/extensions/{{ XWIKI_EXT_LDAP_ID | urlencode }}" vars:
method: GET ext_id: "{{ XWIKI_EXT_LDAP_ID }}"
user: "{{ XWIKI_SUPERADMIN_USERNAME }}" ext_enabled: "{{ XWIKI_LDAP_ENABLED }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}" result_var: "xwiki_ldap_ext"
force_basic_auth: yes
status_code: [200, 404, 302]
register: xwiki_ldap_ext
when: XWIKI_LDAP_ENABLED | bool
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "Install LDAP and/or OIDC extensions" - name: "Install LDAP and/or OIDC extensions"
uri: uri:
url: "{{ XWIKI_REST_EXTENSION_INSTALL }}?jobType=install&async=false" url: "{{ XWIKI_REST_EXTENSION_INSTALL }}?jobType=install&async=false&media=json"
method: PUT method: PUT
user: "{{ XWIKI_SUPERADMIN_USERNAME }}" user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}" password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: yes force_basic_auth: true
headers: headers:
Content-Type: "text/xml" Content-Type: "text/xml"
Accept: "application/xml" Accept: "application/json"
X-Requested-With: "XMLHttpRequest" X-Requested-With: "XMLHttpRequest"
body: "{{ lookup('template', 'installjobrequest.xml.j2') }}" body: "{{ lookup('template', 'installjobrequest.xml.j2') }}"
status_code: [200, 202] status_code: [200, 202]
return_content: yes
when: when:
- (XWIKI_OIDC_ENABLED | bool and xwiki_oidc_ext.status == 404) or - (XWIKI_OIDC_ENABLED | bool and (xwiki_oidc_ext.status | default(404)) != 200)
(XWIKI_LDAP_ENABLED | bool and (xwiki_ldap_ext is not skipped) and xwiki_ldap_ext.status == 404) or
(XWIKI_LDAP_ENABLED | bool and (xwiki_ldap_ext is not skipped) and (xwiki_ldap_ext.status | default(404)) != 200)
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
register: xwiki_install_job
- name: "Extract install job id"
set_fact:
xwiki_install_job_id: "{{ xwiki_install_job | xwiki_job_id(default='') }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "Verify OIDC extension is installed" - name: "Poll install job status until FINISHED without errors"
when: XWIKI_OIDC_ENABLED | bool when: xwiki_install_job_id is defined and xwiki_install_job_id|length > 0
uri: uri:
url: "{{ XWIKI_REST_XWIKI }}/extensions/{{ XWIKI_EXT_OIDC_ID | urlencode }}" url: "{{ XWIKI_REST_BASE }}jobstatus/{{ xwiki_install_job_id }}?media=json"
method: GET method: GET
user: "{{ XWIKI_SUPERADMIN_USERNAME }}" user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}" password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
force_basic_auth: yes force_basic_auth: true
headers:
Accept: "application/json"
status_code: 200 status_code: 200
register: _oidc_ok return_content: yes
retries: 12 register: _install_status
delay: 5 changed_when: false
until: _oidc_ok is succeeded retries: 20
delay: 3
until:
- _install_status is succeeded
- _install_status.json.state == 'FINISHED'
- (_install_status.json.errors | default([]) | length) == 0

View File

@@ -0,0 +1,22 @@
- name: "XWIKI | Probe extension {{ ext_id }}"
when: ext_enabled | bool
uri:
url: "{{ XWIKI_REST_XWIKI }}/extensions/{{ ext_id | 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, 302]
register: _probe
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
changed_when: false
- name: "XWIKI | Save probe result for {{ ext_id }}"
when: ext_enabled | bool
set_fact:
"{{ result_var }}": "{{ _probe }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"