mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
The swarm-compat lints (`compose-chdir-in-task`, `compose-verb-in-task`, `docker-compose-raw-call`) currently flag every offending line and only accept per-line / file-head `# nocheck:` markers. That is noisy for two patterns that are objectively safe: 1. Tasks gated by `when: DEPLOYMENT_MODE \!= 'swarm'` (or `== 'compose'`) never reach a swarm host - the compose verbs inside are legitimate. 2. YAML comment lines, handler-trigger list items, and human-readable metadata fields (`name:`, `msg:`, `title:`, `label:`, `description:`) contain the word `compose` as documentation, not as a shell call. The lints now skip both classes automatically. - utils/annotations/task_gate.py: new helper `is_task_compose_only_gated(lines, idx)`. Walks the surrounding task block via a line-based heuristic and matches `when:` against the compose-only DEPLOYMENT_MODE expressions. Documented as deliberately shallow (no YAML parse). - tests/lint/ansible/shell/test_no_compose_chdir_in_shell_tasks.py and tests/lint/ansible/shell/test_no_compose_verb_in_shell_tasks.py: add a file-level `is_suppressed_in_head` skip, the `is_task_compose_only_gated` skip, and -- for the verb lint -- three extra regex exclusions for notify-list items, YAML metadata fields, and quoted list items. - tests/lint/ansible/shell/test_no_docker_compose_raw_call.py: skip YAML comment lines (bluesky and jenkins each had a `# … docker compose build …` doc-comment that was being flagged as a real call). - tests/unit/utils/annotations/test_task_gate.py: 21 cases covering block-bounds + gate detection, including a `TestKnownLimitations` class that pins the heuristic's blind spots (folded-scalar `when:`, list-form `when:`, `in`/`not in`, unquoted literal, outer-`when:` inheritance into `block:` children) so a future YAML-aware refactor knows exactly which assertions to flip.
69 lines
2.4 KiB
Python
69 lines
2.4 KiB
Python
"""Detect Ansible task-level `when:` gates that exempt compose-only
|
|
shell calls from swarm-compat lints.
|
|
|
|
A task whose `when:` evaluates compose-only at deploy time (e.g.
|
|
`when: DEPLOYMENT_MODE != 'swarm'` or `when: DEPLOYMENT_MODE == 'compose'`)
|
|
never reaches a swarm host, so `compose <verb>` / `chdir: directories.instance`
|
|
inside that task body is legitimate. This module gives the lints a
|
|
heuristic to recognise that gate without parsing YAML.
|
|
|
|
The detection is line-based: the task block is the span between the
|
|
nearest `- name:` (at indent N) above the offending line and the next
|
|
top-level structural marker (`- name:` at indent <= N, or end of
|
|
file). Within that span we look for any `when:` clause whose textual
|
|
value matches a compose-only DEPLOYMENT_MODE expression.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Sequence
|
|
|
|
_TASK_NAME_RE = re.compile(
|
|
r"^(?P<indent>\s*)-\s+(?:name|block|include_tasks|import_tasks|hosts)\s*:"
|
|
)
|
|
_WHEN_RE = re.compile(r"^\s*when\s*:\s*(?P<expr>.+?)\s*$")
|
|
_COMPOSE_ONLY_EXPR = re.compile(
|
|
r"DEPLOYMENT_MODE\s*!=\s*['\"]swarm['\"]"
|
|
r"|DEPLOYMENT_MODE\s*==\s*['\"]compose['\"]"
|
|
)
|
|
|
|
|
|
def _task_block_bounds(lines: Sequence[str], idx: int) -> tuple[int, int]:
|
|
"""Return (start, end_exclusive) of the task block containing *idx*.
|
|
|
|
*start* is the index of the nearest `- name:` (or `- block:`/`- include_tasks:`)
|
|
above *idx*. *end* is the index of the next sibling `- name:` at the same
|
|
indent, or `len(lines)` if none.
|
|
"""
|
|
start = -1
|
|
indent = ""
|
|
for i in range(idx, -1, -1):
|
|
m = _TASK_NAME_RE.match(lines[i])
|
|
if m:
|
|
start = i
|
|
indent = m.group("indent")
|
|
break
|
|
if start < 0:
|
|
return (0, len(lines))
|
|
|
|
for j in range(start + 1, len(lines)):
|
|
m = _TASK_NAME_RE.match(lines[j])
|
|
if m and len(m.group("indent")) <= len(indent):
|
|
return (start, j)
|
|
return (start, len(lines))
|
|
|
|
|
|
def is_task_compose_only_gated(lines: Sequence[str], idx: int) -> bool:
|
|
"""True iff the task body containing *idx* carries a `when:` clause
|
|
whose textual expression evaluates compose-only at deploy time."""
|
|
start, end = _task_block_bounds(lines, idx)
|
|
for k in range(start, end):
|
|
m = _WHEN_RE.match(lines[k])
|
|
if m and _COMPOSE_ONLY_EXPR.search(m.group("expr")):
|
|
return True
|
|
return False
|