mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-09-14 06:17:14 +02:00
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:
@@ -6,7 +6,7 @@ Empower your organization with **XWiki**, an open-source enterprise wiki and kno
|
||||
|
||||
## 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
|
||||
|
||||
|
82
roles/web-app-xwiki/filter_plugins/xwiki_filters.py
Normal file
82
roles/web-app-xwiki/filter_plugins/xwiki_filters.py
Normal 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,
|
||||
}
|
@@ -1,55 +1,60 @@
|
||||
- name: "Check if OIDC extension installed"
|
||||
uri:
|
||||
url: "{{ XWIKI_REST_XWIKI }}/extensions/{{ XWIKI_EXT_OIDC_ID | urlencode }}"
|
||||
method: GET
|
||||
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
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: "Probe OIDC extension"
|
||||
include_tasks: _probe_extension.yml
|
||||
vars:
|
||||
ext_id: "{{ XWIKI_EXT_OIDC_ID }}"
|
||||
ext_enabled: "{{ XWIKI_OIDC_ENABLED }}"
|
||||
result_var: "xwiki_oidc_ext"
|
||||
|
||||
- name: "Check if LDAP extension installed"
|
||||
uri:
|
||||
url: "{{ XWIKI_REST_XWIKI }}/extensions/{{ XWIKI_EXT_LDAP_ID | urlencode }}"
|
||||
method: GET
|
||||
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
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: "Probe LDAP extension"
|
||||
include_tasks: _probe_extension.yml
|
||||
vars:
|
||||
ext_id: "{{ XWIKI_EXT_LDAP_ID }}"
|
||||
ext_enabled: "{{ XWIKI_LDAP_ENABLED }}"
|
||||
result_var: "xwiki_ldap_ext"
|
||||
|
||||
- name: "Install LDAP and/or OIDC extensions"
|
||||
uri:
|
||||
url: "{{ XWIKI_REST_EXTENSION_INSTALL }}?jobType=install&async=false"
|
||||
url: "{{ XWIKI_REST_EXTENSION_INSTALL }}?jobType=install&async=false&media=json"
|
||||
method: PUT
|
||||
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: yes
|
||||
force_basic_auth: true
|
||||
headers:
|
||||
Content-Type: "text/xml"
|
||||
Accept: "application/xml"
|
||||
Accept: "application/json"
|
||||
X-Requested-With: "XMLHttpRequest"
|
||||
body: "{{ lookup('template', 'installjobrequest.xml.j2') }}"
|
||||
status_code: [200, 202]
|
||||
return_content: yes
|
||||
when:
|
||||
- (XWIKI_OIDC_ENABLED | bool and xwiki_oidc_ext.status == 404) or
|
||||
(XWIKI_LDAP_ENABLED | bool and (xwiki_ldap_ext is not skipped) and xwiki_ldap_ext.status == 404)
|
||||
- (XWIKI_OIDC_ENABLED | bool and (xwiki_oidc_ext.status | default(404)) != 200)
|
||||
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 }}"
|
||||
|
||||
- name: "Verify OIDC extension is installed"
|
||||
when: XWIKI_OIDC_ENABLED | bool
|
||||
- name: "Poll install job status until FINISHED without errors"
|
||||
when: xwiki_install_job_id is defined and xwiki_install_job_id|length > 0
|
||||
uri:
|
||||
url: "{{ XWIKI_REST_XWIKI }}/extensions/{{ XWIKI_EXT_OIDC_ID | urlencode }}"
|
||||
url: "{{ XWIKI_REST_BASE }}jobstatus/{{ xwiki_install_job_id }}?media=json"
|
||||
method: GET
|
||||
user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: yes
|
||||
force_basic_auth: true
|
||||
headers:
|
||||
Accept: "application/json"
|
||||
status_code: 200
|
||||
register: _oidc_ok
|
||||
retries: 12
|
||||
delay: 5
|
||||
until: _oidc_ok is succeeded
|
||||
return_content: yes
|
||||
register: _install_status
|
||||
changed_when: false
|
||||
retries: 20
|
||||
delay: 3
|
||||
until:
|
||||
- _install_status is succeeded
|
||||
- _install_status.json.state == 'FINISHED'
|
||||
- (_install_status.json.errors | default([]) | length) == 0
|
||||
|
22
roles/web-app-xwiki/tasks/_probe_extension.yml
Normal file
22
roles/web-app-xwiki/tasks/_probe_extension.yml
Normal 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 }}"
|
Reference in New Issue
Block a user