From 74bf74aa78e8aef68c6210b13be43ce2cdd00a43 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Thu, 8 Oct 2026 09:00:22 +0200 Subject: [PATCH] feat(design): sync one task file of a deployed role A role sync runs the whole role, several hundred tasks, although a design carrier usually lives in one task file. Iterating on that file cost minutes per try. `make compose-role-sync role= variant= task=` now runs only that task file of the role against the running stack. The sync playbook gets a third scope `task` next to `design` and `role`: it places the shared services like a role sync, loads the token store and the proxy handlers, includes the role with `tasks_from` and flushes its handlers. The task file gets the variables of the role and nothing an earlier task of the role would have set, so it has to stand alone. The script accepts a task path of plain path characters only, because the value travels into a command line inside the container. Co-Authored-By: Claude Opus 5.5 (1M context) --- Makefile | 6 ++++-- playbook-sync.yml | 9 ++++++--- scripts/tests/deploy/local/sync/role.sh | 12 +++++++++++- tasks/utils/sync/task.yml | 17 +++++++++++++++++ 4 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 tasks/utils/sync/task.yml diff --git a/Makefile b/Makefile index 05fa368cac..515fd3d43f 100644 --- a/Makefile +++ b/Makefile @@ -245,16 +245,18 @@ compose-restart: .PHONY: compose-role-sync # Re-run only the role of a deployed app against the running stack (no system stages; the spec runs only with pw=). -# Usage: make compose-role-sync role= [variant=] [pw=""] [keep=true] [base=''] +# Usage: make compose-role-sync role= [variant=] [task=] [pw=""] [keep=true] [base=''] # Example: make compose-role-sync role=web-app-gitea variant=0 pw="--grep design: --grep-invert gallery" +# Example: make compose-role-sync role=web-app-xwiki variant=0 task=06_design.yml # Param role: deployed application id # Param variant: matrix round index the app was deployed with +# Param task: task file of the role that runs alone instead of the whole role; it gets the role variables and nothing an earlier task of the role set # Param pw: Playwright arguments without quotes; when set, the spec of the role is staged again, its .env rendered again and run with them # Param keep: true lets that spec run capture the design gallery # Param base: hex base color that replaces the one of the inventory for this run, e.g. base='#001f3f' compose-role-sync: @: $${role:?role= required, e.g. role=web-app-gitea} - @role='$(role)' variant='$(variant)' pw='$(pw)' keep='$(keep)' base='$(base)' bash scripts/tests/deploy/local/sync/role.sh + @role='$(role)' variant='$(variant)' task='$(task)' pw='$(pw)' keep='$(keep)' base='$(base)' bash scripts/tests/deploy/local/sync/role.sh .PHONY: compose-stop # Stop the development stack without removing volumes. diff --git a/playbook-sync.yml b/playbook-sync.yml index 841bebce15..2544b4e95b 100644 --- a/playbook-sync.yml +++ b/playbook-sync.yml @@ -19,14 +19,17 @@ vars: sync_application_id: "{{ APPLICATIONS_WHITELIST | first }}" sync_scope: design + sync_task: "" sync_playwright: false tasks: - name: ✅ Assert exactly one application and a known scope are targeted ansible.builtin.assert: that: - APPLICATIONS_WHITELIST | length == 1 - - sync_scope in ['design', 'role'] - fail_msg: "Pass one application id in APPLICATIONS_WHITELIST and sync_scope=design|role, got {{ APPLICATIONS_WHITELIST }} and {{ sync_scope }}" + - sync_scope in ['design', 'role', 'task'] + - sync_scope != 'task' or sync_task | length > 0 + fail_msg: "Pass one application id in APPLICATIONS_WHITELIST and sync_scope=design|role|task (task needs sync_task), got {{ APPLICATIONS_WHITELIST }}, {{ + sync_scope }} and '{{ sync_task }}'" quiet: true - name: 🔀 Merge variables @@ -34,7 +37,7 @@ - name: "📍 Place the shared services and the proxy on their hosts" ansible.builtin.include_tasks: tasks/utils/constructor/placement.yml - when: sync_scope == 'role' + when: sync_scope in ['role', 'task'] - name: 🧊 Render the applications SPOT once and carry it into every task's worker applications_carrier: {} diff --git a/scripts/tests/deploy/local/sync/role.sh b/scripts/tests/deploy/local/sync/role.sh index ea4cc5c3b0..9a97a77a43 100755 --- a/scripts/tests/deploy/local/sync/role.sh +++ b/scripts/tests/deploy/local/sync/role.sh @@ -4,10 +4,20 @@ # Param pw: Playwright arguments without quotes; when set, the spec of the role is staged again, its .env rendered again and run with them (optional). # Param keep: true lets that spec run capture the design gallery (optional). # Param base: hex base color that replaces the one of the inventory for this run, e.g. '#001f3f' (optional). +# Param task: task file of the role, relative to its tasks directory, that runs alone instead of the whole role, e.g. 06_design.yml (optional). It gets the variables of the role and nothing an earlier task of the role would have set. set -euo pipefail : "${role:?role= required}" +scope="role" +if [[ -n "${task:-}" ]]; then + [[ "${task}" =~ ^[A-Za-z0-9_./-]+$ ]] || { + echo "task must be a task file path of the role: ${task}" >&2 + exit 2 + } + scope="task" +fi + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../../../../.." && pwd)" cd "${REPO_ROOT}" @@ -16,7 +26,7 @@ source scripts/meta/env/load.sh inv_dir="${INFINITO_INVENTORY_DIR}${variant:+-${variant}}" -extra="{\"APPLICATIONS_WHITELIST\": [\"${role}\"], \"sync_scope\": \"role\"" +extra="{\"APPLICATIONS_WHITELIST\": [\"${role}\"], \"sync_scope\": \"${scope}\", \"sync_task\": \"${task:-}\"" if [[ -n "${base:-}" ]]; then extra+=", \"DESIGN_BASE_COLOR\": \"${base}\"" fi diff --git a/tasks/utils/sync/task.yml b/tasks/utils/sync/task.yml new file mode 100644 index 0000000000..9624cef33e --- /dev/null +++ b/tasks/utils/sync/task.yml @@ -0,0 +1,17 @@ +--- +- name: 📂 Include Token Store + ansible.builtin.include_role: + name: sys-token-store + +- name: 🎛️ Load the proxy handlers an app role may notify before its stack + ansible.builtin.include_tasks: "{{ lookup('path_absolute', 'tasks/utils/load/handlers.yml') }}" + vars: + handler_role_name: svc-prx-openresty + +- name: "🔧 Run '{{ sync_task }}' of '{{ sync_application_id }}'" + ansible.builtin.include_role: + name: "{{ sync_application_id }}" + tasks_from: "{{ sync_task }}" + +- name: 🚿 Flush the handlers of the role + ansible.builtin.meta: flush_handlers