fix(sys-svc-compose): replicate rendered templates to every swarm node

The swarm branch placed each rendered file on the peers through a body gated
`when: not (IS_STACK_HOST | bool)`, which assumes the play carries the worker
hosts. A role included as a partner of another stack runs on the stack host
alone, so that body only ever evaluated on the manager, where it skipped. The
odoo round of run 37553816857 shows both halves:

    create /opt/compose/nextcloud/volumes/infinito -> changed: [mgr-01]
    [swarm] Ensure parent directory exists on peer -> skipping: [mgr-01] x16

sixteen skips, one per template, and no worker touched. web-app-nextcloud
mounts that directory as a `type: bind` volume of php config fragments, so
the scheduler put nextcloud on a worker and swarm answered

    invalid mount config for type "bind": bind source path does not exist:
    /opt/compose/nextcloud/volumes/infinito

Address the nodes through the node_hosts lookup instead. 7d9a63c98b
introduced it for this failure class in svc-ai-s1 and
svc-ai-libretranslate-engine, where the symptom was the same and the tell was
the same single result line for the manager; the template path was the one
place still deciding the host set from the play. The block stays behind
IS_SWARM_MODE, so compose is untouched, and the lookup degrades to the
current host where no swarm group exists.

The node set is a strict superset of the previous one: the manager is added,
which the render task already wrote to, and svc-swarm-node carries manager
and workers only, so no file reaches a host class that did not already hold
it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-10-08 02:09:37 +02:00
1 parent 32aa244c8e
commit 155b35ec54
1 file changed
+20 -16
@@ -31,20 +31,24 @@
register: _role_templates_slurp
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "[swarm] Place '{{ _tpl.dest }}' on the non-manager peers"
when: not (IS_STACK_HOST | bool)
block:
- name: "[swarm] Ensure parent directory exists on peer for '{{ _tpl.dest }}'"
ansible.builtin.file:
path: "{{ _tpl.dest | dirname }}"
state: directory
mode: "0755"
# Exception: a role included as a partner runs on the stack host alone, so a `not IS_STACK_HOST` body never evaluates anywhere and every worker stays without the file.
- name: "[swarm] Ensure parent directory of '{{ _tpl.dest }}' exists on every node"
ansible.builtin.file:
path: "{{ _tpl.dest | dirname }}"
state: directory
mode: "0755"
delegate_to: "{{ item }}"
loop: "{{ query('node_hosts') }}"
run_once: true
- name: "[swarm] Distribute '{{ _tpl.dest }}' to swarm peers"
ansible.builtin.copy:
content: "{{ _role_templates_slurp.content | b64decode }}"
dest: "{{ _tpl.dest }}"
owner: "{{ _tpl.owner }}"
group: "{{ _tpl.group }}"
mode: "{{ _tpl.mode }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "[swarm] Distribute '{{ _tpl.dest }}' to every node"
ansible.builtin.copy:
content: "{{ _role_templates_slurp.content | b64decode }}"
dest: "{{ _tpl.dest }}"
owner: "{{ _tpl.owner }}"
group: "{{ _tpl.group }}"
mode: "{{ _tpl.mode }}"
delegate_to: "{{ item }}"
loop: "{{ query('node_hosts') }}"
run_once: true
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"