Files
computer-playbook/roles/web-app-xwiki/files/extension_installer_b64.groovy
kevinveenbirkenbachandClaude Opus 5.5 e142d07f97 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) <noreply@anthropic.com>
2026-10-06 09:17:15 +02:00

57 lines
2.0 KiB
Groovy

// 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 && (!ver || already.id?.version?.toString() == ver)) { println "ALREADY_INSTALLED::${id}::${already.id?.version}"; return }
println "INSTALL_START::${id}::${ver ? ver : 'latest'}"
def job = null
def caughtErr = null
try {
job = ver ? ext.install(id, ver, ns) : ext.install(id, null, ns)
job?.join()
} catch (Throwable t) {
caughtErr = (t?.message ?: t?.toString())
}
def st = job?.status?.state?.name()
if (st) println "STATE=${st}::${id}"
def err = job?.status?.error // singular!
def errMsg = err ? (err?.message ?: err?.toString()) : null
def now = ext.getInstalledExtension(id, ns)
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}"
if (errMsg) println "WARN::${id}::${errMsg}"
println "INSTALLED_OK::${id}::${now.id?.version}"
} else {
if (caughtErr) println "ERROR::${id}::${caughtErr}"
if (errMsg) println "ERROR::${id}::${errMsg}"
if (!caughtErr && !errMsg && st && st != 'FINISHED') {
println "ERROR::${id}::Unexpected install state ${st}"
}
println now ? "ERROR::${id}::Still on ${now.id?.version}, wanted ${ver}" : "INSTALLED_MISSING::${id}"
}
}