feat(ansible): playbooks self-describe via "# description:" comment
CI / lint (push) Successful in 3s
CI / unit (push) Successful in 8s
CI / integration (push) Successful in 2m15s
CI / publish (push) Successful in 52s

Playbooks can ship a human description Steward reads and shows when one is
selected. Convention: a `# description: <text>` magic comment (Ansible rejects
unknown play keys, so a comment is the portable place — works for third-party
playbooks too); falls back to the first play's name:. sources
.discover_playbook_description().

Surfaced at the top of the shared _playbook_vars.html partial, which loads on
playbook selection in the host run form, schedules form, and browse run form.
All four bundled playbooks (provision/install/update/docker_prune) now carry a
description line. Unit tests added.

Scribe #900.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 11:25:25 -04:00
parent a0d1c5f07c
commit e5f6a11f94
8 changed files with 75 additions and 6 deletions
+26
View File
@@ -17,6 +17,10 @@ _SECRET_VAR_RE = re.compile(
r"(password|passwd|secret|token|api[_-]?key|private[_-]?key|credential)", re.I
)
# A playbook self-describes via a `# description:` magic comment (Ansible rejects
# unknown play keys, so a comment is the portable place). First match wins.
_DESCRIPTION_RE = re.compile(r"^\s*#\s*description:\s*(.+?)\s*$", re.I | re.M)
# Name of the always-present, read-only source of first-party playbooks shipped
# inside the app (maintenance tasks, host-agent install). Not operator-editable.
BUILTIN_SOURCE_NAME = "steward-builtin"
@@ -195,6 +199,28 @@ def delete_playbook(source_path: str, relative_path: str) -> tuple[bool, str | N
return True, None
def discover_playbook_description(content: str) -> str:
"""A human-readable description of what a playbook does.
Reads a ``# description: ...`` magic comment (first match), so any playbook
can self-describe without touching its YAML structure. Falls back to the
first play's ``name:``. Returns "" if neither is present.
"""
m = _DESCRIPTION_RE.search(content)
if m:
return m.group(1).strip()
import yaml
try:
plays = yaml.safe_load(content)
except yaml.YAMLError:
return ""
if isinstance(plays, list):
for play in plays:
if isinstance(play, dict) and play.get("name"):
return str(play["name"]).strip()
return ""
def discover_playbook_variables(content: str) -> list[dict]:
"""Parse a playbook and list the variables an operator can set at run time.