mirror of
				https://github.com/kevinveenbirkenbach/computer-playbook.git
				synced 2025-11-04 04:08:15 +00:00 
			
		
		
		
	feat(web-app-xwiki): install OIDC/LDAP via temporary Groovy page (PUT→execute→verify→delete)
Replace REST jobs flow with services.extension.install executed from a transient XWiki.InstallExtensions page. - Build wishlist from Ansible vars; print machine-readable markers; assert success. - Execute from XWiki space; delete page afterwards; fix delete changed_when. - Use Jinja raw + indent for clean macro embedding. https://chatgpt.com/share/68c9ebf5-f5e0-800f-9b80-372b4b31e772
This commit is contained in:
		@@ -1,152 +1,135 @@
 | 
			
		||||
# roles/web-app-xwiki/tasks/04_extensions.yml
 | 
			
		||||
#
 | 
			
		||||
# Canonical install flow for XWiki 16.10.x in this setup:
 | 
			
		||||
# - PUT /rest/jobs with query params (install, async=false, media=json, namespaces, extensions)
 | 
			
		||||
# - Pass lists via <rest><list>…</list></rest> in query parameters
 | 
			
		||||
# - Minimal XML body (<jobRequest/>) just to satisfy the REST layer
 | 
			
		||||
# - Restart once (handler: docker compose restart), then verify via the 'installed' repository
 | 
			
		||||
# Installs OIDC / LDAP using a temporary Groovy page that calls the
 | 
			
		||||
# Extension Script Service (services.extension.install).
 | 
			
		||||
# Avoids REST job API and any Namespace class import for portability.
 | 
			
		||||
#
 | 
			
		||||
# Requires in xwiki.properties:
 | 
			
		||||
# extension.repositories=xwiki-public:maven:https://nexus.xwiki.org/nexus/content/groups/public/,central:maven:https://repo1.maven.org/maven2/
 | 
			
		||||
# Flow:
 | 
			
		||||
#  - Bootstrap config renders with both auth backends OFF (already in your role).
 | 
			
		||||
#  - This file installs required extensions on the current wiki.
 | 
			
		||||
#  - Final config later turns auth ON (already in your role).
 | 
			
		||||
#
 | 
			
		||||
# Notes:
 | 
			
		||||
#  - We print machine-readable markers so Ansible can assert deterministically.
 | 
			
		||||
#  - We protect XWiki's {{groovy}} wiki macro from Jinja by using {% raw %}…{% endraw %}.
 | 
			
		||||
 | 
			
		||||
# 1) Probe what is already installed
 | 
			
		||||
- name: "Probe OIDC extension"
 | 
			
		||||
  include_tasks: _probe_extension.yml
 | 
			
		||||
  vars:
 | 
			
		||||
    ext_id: "{{ XWIKI_EXT_OIDC_ID }}"
 | 
			
		||||
    ext_version: "{{ XWIKI_EXT_OIDC_VERSION }}"
 | 
			
		||||
    ext_enabled: "{{ XWIKI_OIDC_ENABLED }}"
 | 
			
		||||
    result_var: "xwiki_oidc_ext"
 | 
			
		||||
 | 
			
		||||
- name: "Probe LDAP extension"
 | 
			
		||||
  include_tasks: _probe_extension.yml
 | 
			
		||||
  vars:
 | 
			
		||||
    ext_id: "{{ XWIKI_EXT_LDAP_ID }}"
 | 
			
		||||
    ext_version: "{{ XWIKI_EXT_LDAP_VERSION }}"
 | 
			
		||||
    ext_enabled: "{{ XWIKI_LDAP_ENABLED }}"
 | 
			
		||||
    result_var: "xwiki_ldap_ext"
 | 
			
		||||
 | 
			
		||||
# 2) Build the list of extensions to install (enabled + missing)
 | 
			
		||||
- name: "Initialize extension install list"
 | 
			
		||||
- name: "XWIKI | Build Groovy installer code (no wiki macro delimiters here)"
 | 
			
		||||
  set_fact:
 | 
			
		||||
    extensions_to_install: []
 | 
			
		||||
    _install_code: |
 | 
			
		||||
      def ext = services.extension
 | 
			
		||||
      def ns  = "wiki:xwiki"
 | 
			
		||||
 | 
			
		||||
- name: "Queue OIDC extension for install"
 | 
			
		||||
  when:
 | 
			
		||||
    - XWIKI_OIDC_ENABLED | bool
 | 
			
		||||
    - xwiki_oidc_ext.status | int != 200
 | 
			
		||||
  set_fact:
 | 
			
		||||
    extensions_to_install: "{{ extensions_to_install + [ {'id': XWIKI_EXT_OIDC_ID, 'version': XWIKI_EXT_OIDC_VERSION} ] }}"
 | 
			
		||||
      // 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 %}
 | 
			
		||||
 | 
			
		||||
- name: "Queue LDAP extension for install"
 | 
			
		||||
  when:
 | 
			
		||||
    - XWIKI_LDAP_ENABLED | bool
 | 
			
		||||
    - xwiki_ldap_ext is defined
 | 
			
		||||
    - xwiki_ldap_ext.status | int != 200
 | 
			
		||||
  set_fact:
 | 
			
		||||
    extensions_to_install: "{{ extensions_to_install + [ {'id': XWIKI_EXT_LDAP_ID, 'version': XWIKI_EXT_LDAP_VERSION} ] }}"
 | 
			
		||||
      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)
 | 
			
		||||
 | 
			
		||||
# 3) Build XML payloads for the request BODY (not query params)
 | 
			
		||||
- name: "Build XML payloads for job body"
 | 
			
		||||
  when: extensions_to_install | length > 0
 | 
			
		||||
  set_fact:
 | 
			
		||||
    namespaces_xml_rest: "<rest><list><string>wiki:xwiki</string></list></rest>"
 | 
			
		||||
    extensions_xml_rest: >-
 | 
			
		||||
      <rest><list>{% for ext in extensions_to_install -%}
 | 
			
		||||
      <extension><id>{{ ext.id }}</id><version>{{ ext.version }}</version><namespace>wiki:xwiki</namespace></extension>
 | 
			
		||||
      {%- endfor %}</list></rest>
 | 
			
		||||
            // 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)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
# 4) Install extensions synchronously using BODY properties
 | 
			
		||||
- name: "Install extensions via PUT (body properties, sync)"
 | 
			
		||||
  when: extensions_to_install | length > 0
 | 
			
		||||
            // 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"
 | 
			
		||||
  uri:
 | 
			
		||||
    url: "{{ XWIKI_REST_EXTENSION_INSTALL }}?jobType=install&async=false&media=json"
 | 
			
		||||
    url: "{{ [XWIKI_REST_XWIKI_PAGES, 'InstallExtensions'] | url_join }}"
 | 
			
		||||
    method: PUT
 | 
			
		||||
    user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
 | 
			
		||||
    password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
 | 
			
		||||
    force_basic_auth: true
 | 
			
		||||
    status_code: [200, 201, 202, 204]
 | 
			
		||||
    headers:
 | 
			
		||||
      Accept: "application/json"
 | 
			
		||||
      Content-Type: "application/xml"
 | 
			
		||||
      Accept: "application/xml"
 | 
			
		||||
    body: |
 | 
			
		||||
      <jobRequest xmlns="http://www.xwiki.org">
 | 
			
		||||
        <request>
 | 
			
		||||
          <properties>
 | 
			
		||||
            <property>
 | 
			
		||||
              <key>namespaces</key>
 | 
			
		||||
              <value><![CDATA[{{ namespaces_xml_rest }}]]></value>
 | 
			
		||||
            </property>
 | 
			
		||||
            <property>
 | 
			
		||||
              <key>extensions</key>
 | 
			
		||||
              <value><![CDATA[{{ extensions_xml_rest }}]]></value>
 | 
			
		||||
            </property>
 | 
			
		||||
            <property>
 | 
			
		||||
              <key>interactive</key>
 | 
			
		||||
              <value><rest><boolean>false</boolean></rest></value>
 | 
			
		||||
            </property>
 | 
			
		||||
          </properties>
 | 
			
		||||
        </request>
 | 
			
		||||
      </jobRequest>
 | 
			
		||||
    status_code: [200, 201, 202]
 | 
			
		||||
    return_content: yes
 | 
			
		||||
  register: xwiki_install_job
 | 
			
		||||
  notify: docker compose restart
 | 
			
		||||
  changed_when: xwiki_install_job.json.state == 'FINISHED'
 | 
			
		||||
      <page xmlns="http://www.xwiki.org">
 | 
			
		||||
        <title>InstallExtensions</title>
 | 
			
		||||
        <content><![CDATA[
 | 
			
		||||
        {% raw %}{{groovy}}{% endraw %}
 | 
			
		||||
        {{ _install_code | indent(8, False) }}
 | 
			
		||||
        {% raw %}{{/groovy}}{% endraw %}
 | 
			
		||||
        ]]></content>
 | 
			
		||||
        <syntax>xwiki/2.1</syntax>
 | 
			
		||||
      </page>
 | 
			
		||||
  register: _put_page
 | 
			
		||||
 | 
			
		||||
# 5) Restart once so new classes are loaded
 | 
			
		||||
- name: "Restart XWiki after install"
 | 
			
		||||
  when: extensions_to_install | length > 0
 | 
			
		||||
  meta: flush_handlers
 | 
			
		||||
 | 
			
		||||
# 6) Wait for REST to be ready again
 | 
			
		||||
- name: "Wait until XWiki REST is ready after restart"
 | 
			
		||||
- name: "XWIKI | Execute installer page"
 | 
			
		||||
  uri:
 | 
			
		||||
    url: "{{ XWIKI_REST_BASE }}"
 | 
			
		||||
    status_code: [200, 401]
 | 
			
		||||
    follow_redirects: none
 | 
			
		||||
    return_content: no
 | 
			
		||||
  register: xwiki_rest_up_after
 | 
			
		||||
  changed_when: false
 | 
			
		||||
  retries: 60
 | 
			
		||||
  delay: 5
 | 
			
		||||
  until:
 | 
			
		||||
    - xwiki_rest_up_after is succeeded
 | 
			
		||||
    - (xwiki_rest_up_after.redirected is not defined) or (not xwiki_rest_up_after.redirected)
 | 
			
		||||
    url: "{{ 'http://127.0.0.1:' ~ XWIKI_HOST_PORT ~ '/bin/view/XWiki/InstallExtensions?xpage=plain' }}"
 | 
			
		||||
    method: GET
 | 
			
		||||
    user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
 | 
			
		||||
    password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
 | 
			
		||||
    force_basic_auth: true
 | 
			
		||||
    status_code: [200]
 | 
			
		||||
    return_content: yes
 | 
			
		||||
  register: _exec_page
 | 
			
		||||
 | 
			
		||||
# 7) Re-probe from 'installed' repository
 | 
			
		||||
- name: "Re-probe OIDC extension (installed repo)"
 | 
			
		||||
  include_tasks: _probe_extension.yml
 | 
			
		||||
# 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:
 | 
			
		||||
    ext_id: "{{ XWIKI_EXT_OIDC_ID }}"
 | 
			
		||||
    ext_version: "{{ XWIKI_EXT_OIDC_VERSION }}"
 | 
			
		||||
    ext_enabled: "{{ XWIKI_OIDC_ENABLED }}"
 | 
			
		||||
    result_var: "xwiki_oidc_ext_after"
 | 
			
		||||
    _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: "Re-probe LDAP extension (installed repo)"
 | 
			
		||||
  include_tasks: _probe_extension.yml
 | 
			
		||||
  vars:
 | 
			
		||||
    ext_id: "{{ XWIKI_EXT_LDAP_ID }}"
 | 
			
		||||
    ext_version: "{{ XWIKI_EXT_LDAP_VERSION }}"
 | 
			
		||||
    ext_enabled: "{{ XWIKI_LDAP_ENABLED }}"
 | 
			
		||||
    result_var: "xwiki_ldap_ext_after"
 | 
			
		||||
 | 
			
		||||
# 8) Fail fast if something requested is still missing
 | 
			
		||||
- name: "FAIL: OIDC missing after install"
 | 
			
		||||
  fail:
 | 
			
		||||
    msg: >-
 | 
			
		||||
      OIDC extension ({{ XWIKI_EXT_OIDC_ID }} {{ XWIKI_EXT_OIDC_VERSION }}) is NOT detected
 | 
			
		||||
      after install (HTTP {{ xwiki_oidc_ext_after.status | default('n/a') }}).
 | 
			
		||||
      Check 'extension.repositories' in xwiki.properties and network/DNS.
 | 
			
		||||
  when:
 | 
			
		||||
    - XWIKI_OIDC_ENABLED | bool
 | 
			
		||||
    - xwiki_oidc_ext_after.status | int != 200
 | 
			
		||||
 | 
			
		||||
- name: "FAIL: LDAP missing after install"
 | 
			
		||||
  fail:
 | 
			
		||||
    msg: >-
 | 
			
		||||
      LDAP extension ({{ XWIKI_EXT_LDAP_ID }} {{ XWIKI_EXT_LDAP_VERSION }}) is NOT detected
 | 
			
		||||
      after install (HTTP {{ xwiki_ldap_ext_after.status | default('n/a') }}).
 | 
			
		||||
      Check 'extension.repositories' in xwiki.properties and network/DNS.
 | 
			
		||||
  when:
 | 
			
		||||
    - XWIKI_LDAP_ENABLED | bool
 | 
			
		||||
    - xwiki_ldap_ext_after.status | int != 200
 | 
			
		||||
- name: "XWIKI | Delete installer page"
 | 
			
		||||
  uri:
 | 
			
		||||
    url: "{{ [XWIKI_REST_XWIKI_PAGES, 'InstallExtensions'] | url_join }}"
 | 
			
		||||
    method: DELETE
 | 
			
		||||
    user: "{{ XWIKI_SUPERADMIN_USERNAME }}"
 | 
			
		||||
    password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
 | 
			
		||||
    force_basic_auth: true
 | 
			
		||||
    status_code: [204, 200, 202, 404]
 | 
			
		||||
  register: _delete_page
 | 
			
		||||
  changed_when: _delete_page.status != 404
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user