mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-09-17 15:56:04 +02:00
refactor(xwiki): move extension installer logic into static Groovy file and switch to plugins dict
- Added 'plugins' section in config/main.yml to declare enabled extensions in a structured way - Introduced new static file 'files/extension_installer_b64.groovy' that decodes Base64 JSON of requested plugins - Simplified 04_extensions.yml: now builds installer code from static file and removed hardcoded OIDC/LDAP checks - Dropped redundant XWIKI_EXT_* variables in vars/main.yml - Added XWIKI_PLUGINS fact to collect enabled plugin items from config/main.yml This refactor makes extension installation more generic, easier to unit test, and extendable beyond OIDC/LDAP. See: https://chatgpt.com/share/68ca25e3-cbc4-800f-a45e-2b152369811a
This commit is contained in:
@@ -39,3 +39,25 @@ rbac:
|
|||||||
roles: {}
|
roles: {}
|
||||||
ldap:
|
ldap:
|
||||||
local_enabled: true # Allows local login if LDAP is down
|
local_enabled: true # Allows local login if LDAP is down
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
oidc:
|
||||||
|
enabled: true
|
||||||
|
items:
|
||||||
|
- id: "org.xwiki.contrib.oidc:oidc-authenticator"
|
||||||
|
version: "2.19.2"
|
||||||
|
# - id: "org.xwiki.contrib.oidc:oidc-authenticator-ui"
|
||||||
|
# version: "" # let EM choose a compatible version
|
||||||
|
|
||||||
|
ldap:
|
||||||
|
enabled: false
|
||||||
|
items:
|
||||||
|
- id: "org.xwiki.contrib.ldap:ldap-authenticator"
|
||||||
|
version: "9.15.7"
|
||||||
|
|
||||||
|
# Example for Matomo
|
||||||
|
matomo:
|
||||||
|
enabled: false
|
||||||
|
items:
|
||||||
|
- id: "org.xwiki.contrib:matomo"
|
||||||
|
version: "1.0"
|
||||||
|
43
roles/web-app-xwiki/files/extension_installer_b64.groovy
Normal file
43
roles/web-app-xwiki/files/extension_installer_b64.groovy
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// Reads Base64 JSON from placeholder and avoids any quoting issues.
|
||||||
|
import groovy.json.JsonSlurper
|
||||||
|
import java.util.Base64
|
||||||
|
|
||||||
|
def ext = services.extension
|
||||||
|
def ns = "wiki:xwiki"
|
||||||
|
|
||||||
|
def b64 = '__WANTED_B64__'
|
||||||
|
def json = new String(Base64.decoder.decode(b64), 'UTF-8')
|
||||||
|
def wanted = new JsonSlurper().parseText(json) as List<Map>
|
||||||
|
|
||||||
|
if (!wanted || wanted.isEmpty()) {
|
||||||
|
println "SKIP: no extensions requested"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wanted.each { e ->
|
||||||
|
def id = (e.id ?: "").toString()
|
||||||
|
def ver = (e.version ?: "").toString().trim()
|
||||||
|
if (!id) { println "ERROR::<missing-id>::Empty extension id in wanted list"; return }
|
||||||
|
|
||||||
|
def already = ext.getInstalledExtension(id, ns)
|
||||||
|
if (already) { println "ALREADY_INSTALLED::${id}::${already.id?.version}"; return }
|
||||||
|
|
||||||
|
println "INSTALL_START::${id}::${ver ? ver : 'latest'}"
|
||||||
|
def job
|
||||||
|
try {
|
||||||
|
job = ver ? ext.install(id, ver, ns) : ext.install(id, null, ns)
|
||||||
|
job?.join()
|
||||||
|
} catch (Throwable t) {
|
||||||
|
println "ERROR::${id}::${(t?.message ?: t?.toString())}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def st = job?.status?.state?.name()
|
||||||
|
if (st) println "STATE=${st}::${id}"
|
||||||
|
|
||||||
|
def err = job?.status?.error // singular!
|
||||||
|
if (err) println "ERROR::${id}::${(err?.message ?: err?.toString())}"
|
||||||
|
|
||||||
|
def now = ext.getInstalledExtension(id, ns)
|
||||||
|
if (now) println "INSTALLED_OK::${id}::${now.id?.version}"
|
||||||
|
else println "INSTALLED_MISSING::${id}"
|
||||||
|
}
|
@@ -1,5 +1,3 @@
|
|||||||
# roles/web-app-xwiki/tasks/04_extensions.yml
|
|
||||||
#
|
|
||||||
# Installs OIDC / LDAP using a temporary Groovy page that calls the
|
# Installs OIDC / LDAP using a temporary Groovy page that calls the
|
||||||
# Extension Script Service (services.extension.install).
|
# Extension Script Service (services.extension.install).
|
||||||
# Avoids REST job API and any Namespace class import for portability.
|
# Avoids REST job API and any Namespace class import for portability.
|
||||||
@@ -13,57 +11,13 @@
|
|||||||
# - We print machine-readable markers so Ansible can assert deterministically.
|
# - We print machine-readable markers so Ansible can assert deterministically.
|
||||||
# - We protect XWiki's {{groovy}} wiki macro from Jinja by using {% raw %}…{% endraw %}.
|
# - We protect XWiki's {{groovy}} wiki macro from Jinja by using {% raw %}…{% endraw %}.
|
||||||
|
|
||||||
- name: "XWIKI | Build Groovy installer code (no wiki macro delimiters here)"
|
- name: "XWIKI | Build Groovy installer code from static file (base64 payload)"
|
||||||
|
vars:
|
||||||
|
_wanted_b64: "{{ XWIKI_PLUGINS | to_json | b64encode }}"
|
||||||
set_fact:
|
set_fact:
|
||||||
_install_code: |
|
_install_code: >-
|
||||||
def ext = services.extension
|
{{ lookup('file', 'roles/web-app-xwiki/files/extension_installer_b64.groovy')
|
||||||
def ns = "wiki:xwiki"
|
| regex_replace('__WANTED_B64__', _wanted_b64) }}
|
||||||
|
|
||||||
// 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 %}
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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"
|
- name: "XWIKI | PUT installer page Main.InstallExtensions"
|
||||||
uri:
|
uri:
|
||||||
@@ -97,36 +51,12 @@
|
|||||||
force_basic_auth: true
|
force_basic_auth: true
|
||||||
status_code: [200]
|
status_code: [200]
|
||||||
return_content: yes
|
return_content: yes
|
||||||
timeout: 300 # allow up to 5 minutes per attempt
|
timeout: 300
|
||||||
register: _exec_page
|
register: _exec_page
|
||||||
retries: 20 # retry up to 20 times
|
retries: 20
|
||||||
delay: 15 # wait 15 seconds between retries
|
delay: 15
|
||||||
until: _exec_page is succeeded
|
until: _exec_page is succeeded
|
||||||
|
|
||||||
# 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:
|
|
||||||
_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: "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 }}"
|
||||||
|
@@ -44,12 +44,6 @@ XWIKI_REST_XWIKI: "{{ [XWIKI_REST_BASE, 'wikis/xwiki'] | url
|
|||||||
XWIKI_REST_XWIKI_PAGES: "{{ [XWIKI_REST_BASE, 'wikis/xwiki/spaces/XWiki/pages'] | url_join }}"
|
XWIKI_REST_XWIKI_PAGES: "{{ [XWIKI_REST_BASE, 'wikis/xwiki/spaces/XWiki/pages'] | url_join }}"
|
||||||
XWIKI_REST_EXTENSION_INSTALL: "{{ [XWIKI_REST_BASE, 'jobs'] | url_join }}"
|
XWIKI_REST_EXTENSION_INSTALL: "{{ [XWIKI_REST_BASE, 'jobs'] | url_join }}"
|
||||||
|
|
||||||
# Extension IDs + Versions (pin versions explicitly)
|
|
||||||
XWIKI_EXT_LDAP_ID: "org.xwiki.contrib.ldap:ldap-authenticator"
|
|
||||||
XWIKI_EXT_LDAP_VERSION: "9.15.7"
|
|
||||||
XWIKI_EXT_OIDC_ID: "org.xwiki.contrib.oidc:oidc-authenticator"
|
|
||||||
XWIKI_EXT_OIDC_VERSION: "2.19.2"
|
|
||||||
|
|
||||||
# LDAP configuration (mapped to LDAP.* context)
|
# LDAP configuration (mapped to LDAP.* context)
|
||||||
XWIKI_LDAP_SERVER: "{{ LDAP.SERVER.DOMAIN }}"
|
XWIKI_LDAP_SERVER: "{{ LDAP.SERVER.DOMAIN }}"
|
||||||
XWIKI_LDAP_PORT: "{{ LDAP.SERVER.PORT }}"
|
XWIKI_LDAP_PORT: "{{ LDAP.SERVER.PORT }}"
|
||||||
@@ -71,3 +65,11 @@ XWIKI_OIDC_CLIENT_SECRET: "{{ OIDC.CLIENT.SECRET }}"
|
|||||||
XWIKI_OIDC_SCOPES: "openid email profile {{ RBAC.GROUP.CLAIM }}"
|
XWIKI_OIDC_SCOPES: "openid email profile {{ RBAC.GROUP.CLAIM }}"
|
||||||
XWIKI_OIDC_GROUPS_CLAIM: "{{ RBAC.GROUP.CLAIM }}"
|
XWIKI_OIDC_GROUPS_CLAIM: "{{ RBAC.GROUP.CLAIM }}"
|
||||||
XWIKI_OIDC_ADMIN_PROVIDER_GROUP: "{{ [RBAC.GROUP.NAME, XWIKI_ADMIN_GROUP] | path_join }}"
|
XWIKI_OIDC_ADMIN_PROVIDER_GROUP: "{{ [RBAC.GROUP.NAME, XWIKI_ADMIN_GROUP] | path_join }}"
|
||||||
|
|
||||||
|
# Collect enabled plugin items from config/main.yml
|
||||||
|
XWIKI_PLUGINS: >-
|
||||||
|
{{
|
||||||
|
(applications | get_app_conf(application_id, 'plugins'))
|
||||||
|
| dict2items | selectattr('value.enabled','equalto', true)
|
||||||
|
| map(attribute='value.items') | list | sum(start=[])
|
||||||
|
}}
|
||||||
|
Reference in New Issue
Block a user