From 7168474552049ebbcfa1378bc603cc5103e4577a Mon Sep 17 00:00:00 2001 From: Roger Luethi Date: Sat, 8 Aug 2026 19:33:59 +0200 Subject: [PATCH 1/3] manager: require manager_version in part 0 playbooks/deploy.yml already passes manager_version to the "Run manager part 1 + 2" task, but the earlier "Run manager part 0" task never received it, so manager-part-0.yml had no way to tell a stable deploy from a dev-tip one. Pass manager_version explicitly in the "Run manager part 0" shell command, using the already-resolved _manager_version fact (defaults to 'latest' when the caller omits manager_version). In manager-part-0.yml, add an assert as the first task of the "Run manager part 0" play, requiring manager_version to be defined. No default is added to the play's vars block, so a dropped or misspelled argument fails loudly instead of silently falling back to 'latest' -- which would let a stable lane install branch-tip collections while still reporting green. Callers that genuinely want a dev-tip deploy pass 'latest' explicitly. playbooks/deploy.yml is the only caller of manager-part-0.yml, so requiring the variable costs nothing. Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Roger Luethi --- ansible/manager-part-0.yml | 8 ++++++++ playbooks/deploy.yml | 1 + 2 files changed, 9 insertions(+) diff --git a/ansible/manager-part-0.yml b/ansible/manager-part-0.yml index 5f28148ed..7aa1e561d 100644 --- a/ansible/manager-part-0.yml +++ b/ansible/manager-part-0.yml @@ -27,6 +27,14 @@ ansible_galaxy: "{{ venv_path }}/bin/ansible-galaxy" tasks: + - name: Require manager_version + ansible.builtin.assert: + that: manager_version is defined + fail_msg: >- + manager_version must be passed to manager-part-0.yml; pass 'latest' + explicitly for a dev-tip deploy. Defaulting it would let a stable + lane silently install branch-tip collections. + - name: Get home directory of ansible user ansible.builtin.shell: | set -o pipefail diff --git a/playbooks/deploy.yml b/playbooks/deploy.yml index 1a048f573..183ac77cc 100644 --- a/playbooks/deploy.yml +++ b/playbooks/deploy.yml @@ -282,6 +282,7 @@ -e cloud={{ cloud }} \ -e terraform_environment={{ _terraform_environment }} \ -e repo_path={{ repo_path }} \ + -e manager_version={{ _manager_version }} \ manager-part-0.yml | tee -a ansible-manager-part-0.log args: executable: /bin/bash From 7a520d010988c3cec42849274c6cf272fb44e3ec Mon Sep 17 00:00:00 2001 From: Roger Luethi Date: Sat, 8 Aug 2026 19:39:27 +0200 Subject: [PATCH 2/3] manager: reorder collection install tasks Move the two unconditional tasks (creating /usr/share/ansible and installing collections from Ansible Galaxy into it) above the /opt/src group (creating /opt/src directories, syncing sources, and installing local collections) in ansible/manager-part-0.yml. This is a pure move with no behaviour change: no task content was altered, only their order. It is done so that a following commit, which wraps the three /opt/src tasks in a single guarded block, has a readable diff instead of also having to move the local install above the /usr/share/ansible directory it depends on. Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Roger Luethi --- ansible/manager-part-0.yml | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/ansible/manager-part-0.yml b/ansible/manager-part-0.yml index 7aa1e561d..b25012b27 100644 --- a/ansible/manager-part-0.yml +++ b/ansible/manager-part-0.yml @@ -154,6 +154,24 @@ virtualenv: "{{ venv_path }}" virtualenv_command: "{{ venv_command }}" + - name: Create /usr/share/ansible directory + become: true + ansible.builtin.file: + state: directory + path: /usr/share/ansible + mode: 0755 + + - name: Install collections from Ansible galaxy + become: true + ansible.builtin.command: | + {{ ansible_galaxy }} collection install --force --collections-path /usr/share/ansible/collections {{ item }} + register: result + changed_when: "'was installed successfully' in result.stdout" + loop: + - ansible.netcommon + - ansible.posix + - "community.docker>=3.10.2" + - name: Create directories in /opt/src become: true ansible.builtin.file: @@ -175,24 +193,6 @@ - osism/ansible-collection-commons - osism/ansible-collection-services - - name: Create /usr/share/ansible directory - become: true - ansible.builtin.file: - state: directory - path: /usr/share/ansible - mode: 0755 - - - name: Install collections from Ansible galaxy - become: true - ansible.builtin.command: | - {{ ansible_galaxy }} collection install --force --collections-path /usr/share/ansible/collections {{ item }} - register: result - changed_when: "'was installed successfully' in result.stdout" - loop: - - ansible.netcommon - - ansible.posix - - "community.docker>=3.10.2" - - name: Install local collections become: true ansible.builtin.command: | From fee7a22d8374d2622f1678a55bfb81650919d305 Mon Sep 17 00:00:00 2001 From: Roger Luethi Date: Sat, 8 Aug 2026 22:42:08 +0200 Subject: [PATCH 3/3] manager: pin collections to the release .zuul.yaml lists osism/ansible-collection-{commons,services} in required-projects, so Zuul checks them out at branch tip. manager-part-0.yml then rsynced that checkout into /opt/src and force-installed it with no condition, on every lane -- including the ones deploying a pinned stable release. A manager "running 9.5.0" therefore ran main-tip collections, a combination no operator has. That is why testbed-upgrade-stable-ubuntu-24.04 broke when ansible-collection-services removed the osism-update-manager wrapper on main: the 9.5.0 lane picked the removal up overnight even though 9.5.0 pins a collection that still ships the wrapper. When manager_version is not latest, resolve ansible_collections from osism/release//base.yml and install both collections from git at that tag. The URL, the keys and the v-prefix rule mirror generics/src/set-versions.py rather than inventing a convention. Lanes targeting latest keep the /opt/src path. Branch-tip collections are production-accurate there, because the generics run.sh default is ANSIBLE_COLLECTION_SERVICES_VERSION:-main and set-versions.py only overrides it for a named release. Those lanes are also the only integration coverage the collections get, so their speculative Depends-On behaviour has to survive. The pin fetch uses curl rather than uri or get_url. The orchestrator runs ansible-core 2.15.2, whose module_utils/urls.py passes cert_file to HTTPSConnection, and Python 3.12 on the Ubuntu 24.04 manager image removed that parameter, so both modules fail before issuing a request. delegate_to: localhost does not help, because the orchestrator carries the same ansible-core. Both the fetch and the install retry three times and stay fail-closed: if the pin cannot be resolved the deploy fails rather than falling back to branch tip, which would restore the original bug invisibly and leave a green log. Assisted-by: Claude:claude-opus-5 Signed-off-by: Roger Luethi --- ansible/manager-part-0.yml | 105 ++++++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 30 deletions(-) diff --git a/ansible/manager-part-0.yml b/ansible/manager-part-0.yml index b25012b27..2921bed45 100644 --- a/ansible/manager-part-0.yml +++ b/ansible/manager-part-0.yml @@ -172,36 +172,81 @@ - ansible.posix - "community.docker>=3.10.2" - - name: Create directories in /opt/src - become: true - ansible.builtin.file: - state: directory - path: "/opt/src/{{ item }}" - recurse: true - mode: 0755 - owner: "{{ ansible_user }}" - with_items: - - osism/ansible-collection-commons - - osism/ansible-collection-services - - - name: Sync sources in /opt/src - ansible.posix.synchronize: - src: "{{ repo_path }}/{{ item }}" - delete: true - dest: "/opt/src/{{ item }}" - with_items: - - osism/ansible-collection-commons - - osism/ansible-collection-services - - - name: Install local collections - become: true - ansible.builtin.command: | - {{ ansible_galaxy }} collection install --force --collections-path /usr/share/ansible/collections /opt/src/osism/{{ item }} - register: result - changed_when: "'was installed successfully' in result.stdout" - loop: - - ansible-collection-commons - - ansible-collection-services + - name: Install OSISM collections from the Zuul checkout + when: manager_version == "latest" + block: + - name: Create directories in /opt/src + become: true + ansible.builtin.file: + state: directory + path: "/opt/src/{{ item }}" + recurse: true + mode: 0755 + owner: "{{ ansible_user }}" + with_items: + - osism/ansible-collection-commons + - osism/ansible-collection-services + + - name: Sync sources in /opt/src + ansible.posix.synchronize: + src: "{{ repo_path }}/{{ item }}" + delete: true + dest: "/opt/src/{{ item }}" + with_items: + - osism/ansible-collection-commons + - osism/ansible-collection-services + + - name: Install local collections + become: true + ansible.builtin.command: | + {{ ansible_galaxy }} collection install --force --collections-path /usr/share/ansible/collections /opt/src/osism/{{ item }} + register: result + changed_when: "'was installed successfully' in result.stdout" + loop: + - ansible-collection-commons + - ansible-collection-services + + - name: Install OSISM collections at the release pin + when: manager_version != "latest" + block: + # The orchestrator's ansible-core 2.15.2 passes cert_file to + # HTTPSConnection, a parameter Python 3.12 (manager image, Ubuntu + # 24.04) removed -- uri/get_url both die here with "unexpected + # keyword argument 'cert_file'" before any request is made. + # delegate_to: localhost does not help (same ansible-core). Do not + # revert to uri/get_url; use curl. + - name: Fetch release pins # noqa: command-instead-of-module + ansible.builtin.command: | + curl --silent --show-error --fail --location https://raw.githubusercontent.com/osism/release/main/{{ manager_version }}/base.yml + register: _release_base + changed_when: false + retries: 3 + delay: 5 + until: _release_base.rc == 0 + + - name: Resolve collection pins + ansible.builtin.set_fact: + _collection_pins: + ansible-collection-commons: "{{ _c if _c == 'main' else 'v' + _c }}" + ansible-collection-services: "{{ _s if _s == 'main' else 'v' + _s }}" + vars: + _c: "{{ (_release_base.stdout | from_yaml).ansible_collections['osism.commons'] }}" + _s: "{{ (_release_base.stdout | from_yaml).ansible_collections['osism.services'] }}" + + - name: Print resolved collection pins + ansible.builtin.debug: + var: _collection_pins + + - name: Install pinned OSISM collections + become: true + ansible.builtin.command: | + {{ ansible_galaxy }} collection install --force --collections-path /usr/share/ansible/collections git+https://github.com/osism/{{ item.key }},{{ item.value }} + register: result + changed_when: "'was installed successfully' in result.stdout" + retries: 3 + delay: 5 + until: result is succeeded + loop: "{{ _collection_pins | dict2items }}" - name: Create operator user hosts: testbed-manager