feat(tags): scan roles/+playbooks/ and fail on unknown tags
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b45118dac3
commit
a3ea2aceb2
2 changed files with 78 additions and 4 deletions
|
|
@ -29,7 +29,6 @@ def _ignore(loader, tag_suffix, node):
|
|||
|
||||
|
||||
_IgnoreUnknownTags.add_multi_constructor("", _ignore)
|
||||
_IgnoreUnknownTags.add_multi_constructor("!", _ignore)
|
||||
|
||||
|
||||
def _static_str(value):
|
||||
|
|
@ -68,5 +67,52 @@ def collect_tags(node):
|
|||
return tags
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
sys.exit(0)
|
||||
def scan_text(text):
|
||||
"""Collect static tags from a (possibly multi-document) YAML string."""
|
||||
found = set()
|
||||
for doc in yaml.load_all(text, Loader=_IgnoreUnknownTags):
|
||||
found |= collect_tags(doc)
|
||||
return found
|
||||
|
||||
|
||||
def iter_yaml_files(repo=REPO, scan_dirs=SCAN_DIRS):
|
||||
for name in scan_dirs:
|
||||
base = repo / name
|
||||
if not base.is_dir():
|
||||
continue
|
||||
for ext in ("*.yml", "*.yaml"):
|
||||
yield from sorted(base.rglob(ext))
|
||||
|
||||
|
||||
def find_violations(used, allowed):
|
||||
return sorted(used - allowed)
|
||||
|
||||
|
||||
def main():
|
||||
allowed = load_vocab() | role_names()
|
||||
violations = []
|
||||
for path in iter_yaml_files():
|
||||
try:
|
||||
used = scan_text(path.read_text())
|
||||
except yaml.YAMLError as exc:
|
||||
print(f"warning: could not parse {path}: {exc}", file=sys.stderr)
|
||||
continue
|
||||
for tag in find_violations(used, allowed):
|
||||
violations.append((path.relative_to(REPO), tag))
|
||||
|
||||
if violations:
|
||||
print(
|
||||
"error: Ansible tag(s) not in tests/tags.yml or role names "
|
||||
"(see docs/decisions/019-tagging.md):",
|
||||
file=sys.stderr,
|
||||
)
|
||||
for relpath, tag in violations:
|
||||
print(f" {relpath}: '{tag}'", file=sys.stderr)
|
||||
print(f"\nallowed: {', '.join(sorted(allowed))}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(f"check-tags: OK ({len(allowed)} tags allowed across {len(SCAN_DIRS)} dirs)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -35,10 +35,38 @@ def test_load_vocab_unions_all_categories():
|
|||
assert "firewall" in vocab # concern
|
||||
assert "always" in vocab # special
|
||||
assert "bootstrap" in vocab # playbook identity
|
||||
assert len([c for c in vocab]) >= 12
|
||||
assert len(vocab) >= 10
|
||||
|
||||
|
||||
def test_role_names_reads_role_dirs():
|
||||
names = ct.role_names()
|
||||
assert "base" in names
|
||||
assert "docker_host" in names
|
||||
|
||||
|
||||
def test_scan_text_collects_from_yaml_string():
|
||||
text = """
|
||||
- hosts: all
|
||||
roles:
|
||||
- role: base
|
||||
tags: [base]
|
||||
tasks:
|
||||
- name: open port
|
||||
tags: [firewall]
|
||||
"""
|
||||
assert ct.scan_text(text) == {"base", "firewall"}
|
||||
|
||||
|
||||
def test_scan_text_tolerates_custom_yaml_tags():
|
||||
text = "- name: t\n secret: !vault xxx\n tags: [users]\n"
|
||||
assert ct.scan_text(text) == {"users"}
|
||||
|
||||
|
||||
def test_find_violations_flags_unknown_tag():
|
||||
allowed = {"base", "firewall"}
|
||||
used = {"base", "frewall"} # typo
|
||||
assert ct.find_violations(used, allowed) == ["frewall"]
|
||||
|
||||
|
||||
def test_find_violations_empty_when_all_allowed():
|
||||
assert ct.find_violations({"base", "firewall"}, {"base", "firewall"}) == []
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue