fix(lint): revert leaf-only keyword hoists apply: cannot carry

Follow-up to 37583e807. apply: on include_tasks wraps the included tasks in a Block, so it accepts only Block-valid keywords. The earlier broad hoist wrongly moved leaf-only keywords (changed_when, delay, async, poll) into apply:, which Ansible rejects at parse time ("'<kw>' is not a valid attribute for a Block") and would have broken every compose deploy at the post_deploy include.

Revert changed_when/delay (post_deploy.yml + its 3 include sites), changed_when (bigbluebutton, moodle x2), and async/poll (sys-svc-dns) back onto the per-task lines; the include sites keep only the Block-valid apply: keys (delegate_to, run_once) or revert to the shorthand include. test_include_uniform_apply_keyword now blacklists the leaf-only keywords (async/changed_when/delay/failed_when/poll/retries/until) so only Block-valid keywords are flagged for hoisting.

Verified: no apply: block contains a leaf-only keyword; lint test green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 4.8 committed 2026-06-20 00:44:37 +02:00
1 parent db43edfe3a
commit 64eef34401
12 files changed
+43 -28

No files matched your search

@@ -32,5 +32,3 @@
apply:
delegate_to: "{{ STACK_HOST }}"
run_once: true
delay: 2
changed_when: false
@@ -8,5 +8,3 @@
apply:
delegate_to: "{{ STACK_HOST }}"
run_once: true
delay: 2
changed_when: false
@@ -6,6 +6,8 @@
{{ lookup('database', application_id, 'container') }}
register: _app_db_health
retries: 60
delay: 2
changed_when: false
until: (_app_db_health.stdout | trim) == 'healthy'
when:
- DEPLOYMENT_MODE != 'swarm'
@@ -17,5 +19,7 @@
STACK: "{{ application_id | get_entity_name }}"
register: _swarm_tasks_ready
retries: 150
delay: 2
changed_when: false
until: _swarm_tasks_ready is succeeded
when: DEPLOYMENT_MODE == 'swarm'
-2
View File
@@ -40,6 +40,4 @@
apply:
delegate_to: "{{ STACK_HOST }}"
run_once: true
delay: 2
changed_when: false
when: compose_handlers_flush | bool
+1 -5
View File
@@ -6,11 +6,7 @@
when: (CLOUDFLARE_API_TOKEN | default('') | trim) == ''
- name: "Apply apex A/AAAA for base domains"
include_tasks:
file: 02_apex.yml
apply:
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
include_tasks: 02_apex.yml
loop: "{{ SYS_SVC_DNS_BASE_DOMAINS | list }}"
loop_control:
loop_var: base_domain
+4
View File
@@ -9,6 +9,8 @@
proxied: false
ttl: 1
state: present
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
- name: "Ensure AAAA @ for {{ base_domain }} with '{{ networks.internet.ip6 | default('None') }}"
community.general.cloudflare_dns:
@@ -22,3 +24,5 @@
state: present
when:
- (networks.internet.ip6 | default('') | trim) != ''
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
@@ -25,10 +25,7 @@
meta: flush_handlers
- name: "Ensure PostgreSQL password matches POSTGRESQL_SECRET"
ansible.builtin.include_tasks:
file: "02a_postgres_password_sync.yml"
apply:
changed_when: false
ansible.builtin.include_tasks: "02a_postgres_password_sync.yml"
- name: "Setup administrator"
include_tasks: "03_administrator.yml"
@@ -7,6 +7,7 @@
retries: 20
delay: 3
until: pg_ready.rc == 0
changed_when: false
- name: "Wait for PostgreSQL to accept SQL after initdb restarts"
ansible.builtin.shell: >
@@ -16,6 +17,7 @@
retries: 30
delay: 2
until: pg_sql_ready.rc == 0 and (pg_sql_ready.stdout | trim) == '1'
changed_when: false
- name: "Ensure PostgreSQL password matches POSTGRESQL_SECRET"
ansible.builtin.shell: >
@@ -26,3 +28,4 @@
retries: 20
delay: 3
until: bbb_pg_alter.rc == 0
changed_when: false
@@ -74,17 +74,11 @@
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "Include ownership settings tasks for moodle"
ansible.builtin.include_tasks:
file: 02_ownership.yml
apply:
changed_when: false
ansible.builtin.include_tasks: 02_ownership.yml
- name: "Configure OIDC login for Moodle (variant 0 only)"
include_tasks: 03_oidc.yml
when: MOODLE_OIDC_ENABLED | bool
- name: "Configure auth_ldap (sync-only in variant 0, primary login in variant 1)"
ansible.builtin.include_tasks:
file: 04_ldap_sync.yml
apply:
changed_when: false
ansible.builtin.include_tasks: 04_ldap_sync.yml
when: MOODLE_LDAP_ENABLED | bool
@@ -11,21 +11,28 @@
- name: "Ensure ownership is correct on Moodle code dir"
shell: "container exec --user root {{ MOODLE_CONTAINER_ADDRESS }} chown -R {{ MOODLE_RUNTIME_USER_GROUP }} {{ MOODLE_CODE_DIR }}"
changed_when: false
- name: "Ensure ownership is correct on Moodle data dir"
shell: "container exec --user root {{ MOODLE_CONTAINER_ADDRESS }} chown -R {{ MOODLE_RUNTIME_USER_GROUP }} {{ MOODLE_DATA_DIR }}"
changed_when: false
- name: "Code dir: 0755 dirs (nginx-readable)"
shell: "container exec --user root {{ MOODLE_CONTAINER_ADDRESS }} find {{ MOODLE_CODE_DIR }} -type d -exec chmod 0755 {} \\;"
changed_when: false
- name: "Code dir: 0644 files (nginx-readable)"
shell: "container exec --user root {{ MOODLE_CONTAINER_ADDRESS }} find {{ MOODLE_CODE_DIR }} -type f -exec chmod 0644 {} \\;"
changed_when: false
- name: "Code dir: 0640 config.php (FPM-only)"
shell: "container exec --user root {{ MOODLE_CONTAINER_ADDRESS }} chmod 0640 {{ MOODLE_CONFIG }}"
changed_when: false
- name: "Data dir: 0770 dirs (FPM-only)"
shell: "container exec --user root {{ MOODLE_CONTAINER_ADDRESS }} find {{ MOODLE_DATA_DIR }} -type d -exec chmod 0770 {} \\;"
changed_when: false
- name: "Data dir: 0660 files (FPM-only)"
shell: "container exec --user root {{ MOODLE_CONTAINER_ADDRESS }} find {{ MOODLE_DATA_DIR }} -type f -exec chmod 0660 {} \\;"
changed_when: false
@@ -9,6 +9,7 @@
container exec --user {{ MOODLE_RUNTIME_USER }} {{ MOODLE_CONTAINER_ADDRESS }}
php {{ MOODLE_CLI_DIR }}/upgrade.php --non-interactive
failed_when: false
changed_when: false
- name: "Configure auth_ldap connection + field mapping"
loop:
@@ -95,18 +96,21 @@
php {{ MOODLE_CLI_DIR }}/cfg.php --component=auth_ldap
--name={{ item.name }} --set="{{ item.value }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
changed_when: false
- name: "Enable auth_ldap plugin"
shell: >
container exec --user {{ MOODLE_RUNTIME_USER }} {{ MOODLE_CONTAINER_ADDRESS }}
php {{ MOODLE_CLI_DIR }}/cfg.php --name=auth --set=ldap
when: not (MOODLE_OIDC_ENABLED | bool)
changed_when: false
- name: "Variant 0: enable both auth_oidc + auth_ldap (oidc primary)"
shell: >
container exec --user {{ MOODLE_RUNTIME_USER }} {{ MOODLE_CONTAINER_ADDRESS }}
php {{ MOODLE_CLI_DIR }}/cfg.php --name=auth --set=oidc,ldap
when: MOODLE_OIDC_ENABLED | bool
changed_when: false
- name: "Hide auth_ldap from login page in variant 0 (OIDC is the only entry point)"
shell: >
@@ -114,6 +118,7 @@
php {{ MOODLE_CLI_DIR }}/cfg.php --component=auth_ldap
--name=removecontexts --set=1
when: MOODLE_OIDC_ENABLED | bool
changed_when: false
- name: "Trigger auth_ldap sync to pre-provision LDAP users"
shell: >
@@ -121,3 +126,4 @@
php {{ MOODLE_CLI_DIR }}/scheduled_task.php
--execute='\auth_ldap\task\sync_task'
failed_when: false
changed_when: false
@@ -81,25 +81,35 @@ _TASK_KEYWORDS = frozenset(
}
)
# Exception: blacklist of task keywords that must NOT be flagged even when uniform.
# They bind to a single task's identity (name), result (register), arguments
# (args), or iteration (loop*); `vars` changes scoping; `when` belongs on the
# dynamic include's own `when:` (which gates the whole include) rather than
# `apply:`; block/rescue/always are structure, not hoistable directives.
# Blacklist of task keywords that must NOT be flagged even when uniform. Two
# reasons: (1) per-task binding (name/register/args/loop*/vars), block structure
# (block/rescue/always), or the include's own gate (when) are not hoistable;
# (2) LEAF-ONLY keywords (async/changed_when/delay/failed_when/poll/retries/until)
# are not valid Block attributes, and apply: wraps the included tasks in a Block,
# so it rejects them at parse ("'<kw>' is not a valid attribute for a Block").
# Only Block-valid keywords (delegate_to, run_once, become*, no_log, notify,
# tags, environment, connection, ...) can be hoisted to apply:.
_IGNORED_KEYWORDS = frozenset(
{
"action",
"always",
"args",
"async",
"block",
"changed_when",
"collections",
"delay",
"failed_when",
"local_action",
"loop",
"loop_control",
"module_defaults",
"name",
"poll",
"register",
"rescue",
"retries",
"until",
"vars",
"when",
}