From d827b0575f313a30772fe6aa0667b815329ea374 Mon Sep 17 00:00:00 2001 From: "infinito-nexus-updater[bot]" <286144613+infinito-nexus-updater[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 19:20:55 +0000 Subject: [PATCH 1/2] update(versions): update pins with a declared source (web-app-xwiki) --- roles/web-app-xwiki/meta/addons/ai-llm.yml | 2 +- roles/web-app-xwiki/meta/addons/ldap-authenticator.yml | 2 +- roles/web-app-xwiki/meta/addons/oidc-authenticator.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/roles/web-app-xwiki/meta/addons/ai-llm.yml b/roles/web-app-xwiki/meta/addons/ai-llm.yml index 18e7bdec6c..5fa3d267ec 100644 --- a/roles/web-app-xwiki/meta/addons/ai-llm.yml +++ b/roles/web-app-xwiki/meta/addons/ai-llm.yml @@ -5,7 +5,7 @@ mechanism: extension source: upstream bridges: - litellm -version: "0.7" +version: "0.10" update: type: http_regex url: https://maven.xwiki.org/releases/org/xwiki/contrib/llm/application-ai-llm-models-ui/maven-metadata.xml diff --git a/roles/web-app-xwiki/meta/addons/ldap-authenticator.yml b/roles/web-app-xwiki/meta/addons/ldap-authenticator.yml index 3a6372c637..89c20d8112 100644 --- a/roles/web-app-xwiki/meta/addons/ldap-authenticator.yml +++ b/roles/web-app-xwiki/meta/addons/ldap-authenticator.yml @@ -5,7 +5,7 @@ mechanism: extension source: upstream bridges: - ldap -version: "9.15.7" +version: "9.16.6" update: type: http_regex url: https://maven.xwiki.org/releases/org/xwiki/contrib/ldap/ldap-authenticator/maven-metadata.xml diff --git a/roles/web-app-xwiki/meta/addons/oidc-authenticator.yml b/roles/web-app-xwiki/meta/addons/oidc-authenticator.yml index e80ea23ca7..16f95f37ce 100644 --- a/roles/web-app-xwiki/meta/addons/oidc-authenticator.yml +++ b/roles/web-app-xwiki/meta/addons/oidc-authenticator.yml @@ -5,7 +5,7 @@ mechanism: extension source: upstream bridges: - sso -version: "2.19.6" +version: "2.28.1" update: type: http_regex url: https://maven.xwiki.org/releases/org/xwiki/contrib/oidc/oidc-authenticator/maven-metadata.xml From e142d07f97ec6e1b50313bc6e80761f603785418 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Tue, 6 Oct 2026 08:14:14 +0200 Subject: [PATCH 2/2] fix(web-app-xwiki): upgrade an add-on that is not on its pinned version The extension installer returned as soon as an add-on was installed, in whatever version. A new pin in meta/addons/ therefore reached a fresh wiki only, and an existing wiki kept the version it was first deployed with. The installer now skips an add-on only when the installed version equals the pin. Otherwise it calls install(id, version, namespace), which the Extension Manager resolves as an upgrade, and it reports an error when the add-on is not on the pin afterwards. The check that follows the installer compared presence only. It takes the pin as well, so a deploy fails when an add-on is installed in another version. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../files/extension_installer_b64.groovy | 6 +-- .../filter_plugins/xwiki_filters.py | 9 ++-- roles/web-app-xwiki/tasks/04_extensions.yml | 5 ++- .../tasks/_check_extension_via_groovy.yml | 2 +- .../python/roles/web-app-xwiki/__init__.py | 0 .../web-app-xwiki/filter_plugins/__init__.py | 0 .../filter_plugins/test_xwiki_filters.py | 42 +++++++++++++++++++ 7 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 tests/unit/python/roles/web-app-xwiki/__init__.py create mode 100644 tests/unit/python/roles/web-app-xwiki/filter_plugins/__init__.py create mode 100644 tests/unit/python/roles/web-app-xwiki/filter_plugins/test_xwiki_filters.py diff --git a/roles/web-app-xwiki/files/extension_installer_b64.groovy b/roles/web-app-xwiki/files/extension_installer_b64.groovy index 631b51497c..4d6e1d45c9 100644 --- a/roles/web-app-xwiki/files/extension_installer_b64.groovy +++ b/roles/web-app-xwiki/files/extension_installer_b64.groovy @@ -20,7 +20,7 @@ wanted.each { e -> if (!id) { println "ERROR::::Empty extension id in wanted list"; return } def already = ext.getInstalledExtension(id, ns) - if (already) { println "ALREADY_INSTALLED::${id}::${already.id?.version}"; return } + if (already && (!ver || already.id?.version?.toString() == ver)) { println "ALREADY_INSTALLED::${id}::${already.id?.version}"; return } println "INSTALL_START::${id}::${ver ? ver : 'latest'}" def job = null @@ -39,7 +39,7 @@ wanted.each { e -> def errMsg = err ? (err?.message ?: err?.toString()) : null def now = ext.getInstalledExtension(id, ns) - if (now) { + if (now && (!ver || now.id?.version?.toString() == ver)) { // Race-safe: Extension Manager can report "Failed to create install plan" // while the extension gets installed by the concurrent distribution job. if (caughtErr) println "WARN::${id}::${caughtErr}" @@ -51,6 +51,6 @@ wanted.each { e -> if (!caughtErr && !errMsg && st && st != 'FINISHED') { println "ERROR::${id}::Unexpected install state ${st}" } - println "INSTALLED_MISSING::${id}" + println now ? "ERROR::${id}::Still on ${now.id?.version}, wanted ${ver}" : "INSTALLED_MISSING::${id}" } } diff --git a/roles/web-app-xwiki/filter_plugins/xwiki_filters.py b/roles/web-app-xwiki/filter_plugins/xwiki_filters.py index 0a914d474d..4b7eea5c69 100644 --- a/roles/web-app-xwiki/filter_plugins/xwiki_filters.py +++ b/roles/web-app-xwiki/filter_plugins/xwiki_filters.py @@ -3,18 +3,18 @@ from __future__ import annotations import re -def xwiki_extension_status(raw: str) -> int: +def xwiki_extension_status(raw: str, version: 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. + version: The pinned version; empty accepts any installed version. Returns: - 200 if installed, 404 if missing/unknown. + 200 if installed in the pinned version, otherwise 404. """ if raw is None: return 404 @@ -23,7 +23,8 @@ def xwiki_extension_status(raw: str) -> int: text = text.replace(" ", " ").replace("\u00a0", " ") text = text.strip() - if text.startswith("INSTALLED::"): + suffix = f"::{version}" if version else "" + if text.startswith("INSTALLED::") and text.endswith(suffix): return 200 return 404 diff --git a/roles/web-app-xwiki/tasks/04_extensions.yml b/roles/web-app-xwiki/tasks/04_extensions.yml index f93d0eea3d..75d9d8a3be 100644 --- a/roles/web-app-xwiki/tasks/04_extensions.yml +++ b/roles/web-app-xwiki/tasks/04_extensions.yml @@ -72,6 +72,7 @@ label: "{{ plugin.id }}" vars: ext_id: "{{ plugin.id }}" + ext_version: "{{ plugin.version }}" result_var: "probe_{{ plugin.id | regex_replace('[^A-Za-z0-9_]', '_') }}" - name: "📊 XWIKI | Collect probe results" @@ -83,11 +84,11 @@ loop_var: plugin changed_when: false -- name: "✅ XWIKI | Assert all requested extensions are installed" +- name: "✅ XWIKI | Assert all requested extensions are on their pinned version" vars: missing: "{{ (_xwiki_probe_results | default([])) | selectattr('status','equalto',404) | map(attribute='id') | list }}" fail: - msg: "Missing extensions: {{ missing | join(', ') }}" + msg: "Extensions missing or not on their pinned version: {{ missing | join(', ') }}" when: missing | length > 0 - name: "🗑️ XWIKI | Delete installer page" diff --git a/roles/web-app-xwiki/tasks/_check_extension_via_groovy.yml b/roles/web-app-xwiki/tasks/_check_extension_via_groovy.yml index 5e81389e0c..cbbd210660 100644 --- a/roles/web-app-xwiki/tasks/_check_extension_via_groovy.yml +++ b/roles/web-app-xwiki/tasks/_check_extension_via_groovy.yml @@ -32,7 +32,7 @@ - name: "💾 XWIKI | Save Groovy check result for {{ ext_id }}" set_fact: "{{ result_var }}": - status: "{{ _check_output.content | xwiki_extension_status }}" + status: "{{ _check_output.content | xwiki_extension_status(ext_version) }}" raw: "{{ _check_output.content }}" - name: "🗑️ XWIKI | Delete checker page" diff --git a/tests/unit/python/roles/web-app-xwiki/__init__.py b/tests/unit/python/roles/web-app-xwiki/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/unit/python/roles/web-app-xwiki/filter_plugins/__init__.py b/tests/unit/python/roles/web-app-xwiki/filter_plugins/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/unit/python/roles/web-app-xwiki/filter_plugins/test_xwiki_filters.py b/tests/unit/python/roles/web-app-xwiki/filter_plugins/test_xwiki_filters.py new file mode 100644 index 0000000000..87142501ea --- /dev/null +++ b/tests/unit/python/roles/web-app-xwiki/filter_plugins/test_xwiki_filters.py @@ -0,0 +1,42 @@ +import importlib.util +import unittest + +from utils import PROJECT_ROOT + +_spec = importlib.util.spec_from_file_location( + "xwiki_filters", + PROJECT_ROOT / "roles/web-app-xwiki/filter_plugins/xwiki_filters.py", +) +_mod = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(_mod) +xwiki_extension_status = _mod.xwiki_extension_status + +EXTENSION = "org.xwiki.contrib.oidc:oidc-authenticator" + + +class TestExtensionStatus(unittest.TestCase): + def test_the_pinned_version_counts_as_installed(self): + page = f"

INSTALLED::{EXTENSION}::2.28.1

" + self.assertEqual(xwiki_extension_status(page, "2.28.1"), 200) + + def test_another_installed_version_is_not_the_pinned_one(self): + page = f"INSTALLED::{EXTENSION}::2.19.6" + self.assertEqual(xwiki_extension_status(page, "2.28.1"), 404) + + def test_a_version_does_not_pass_for_one_it_only_starts_or_ends_like(self): + self.assertEqual( + xwiki_extension_status(f"INSTALLED::{EXTENSION}::0.10", "0.1"), 404 + ) + self.assertEqual( + xwiki_extension_status(f"INSTALLED::{EXTENSION}::10.1", "0.1"), 404 + ) + + def test_no_pin_accepts_any_installed_version(self): + self.assertEqual(xwiki_extension_status(f"INSTALLED::{EXTENSION}::2.19.6"), 200) + + def test_a_missing_extension_is_not_installed(self): + self.assertEqual(xwiki_extension_status(f"MISSING::{EXTENSION}", "2.28.1"), 404) + + +if __name__ == "__main__": + unittest.main()