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)
|
||||||
_IgnoreUnknownTags.add_multi_constructor("!", _ignore)
|
|
||||||
|
|
||||||
|
|
||||||
def _static_str(value):
|
def _static_str(value):
|
||||||
|
|
@ -68,5 +67,52 @@ def collect_tags(node):
|
||||||
return tags
|
return tags
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__": # pragma: no cover
|
def scan_text(text):
|
||||||
sys.exit(0)
|
"""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 "firewall" in vocab # concern
|
||||||
assert "always" in vocab # special
|
assert "always" in vocab # special
|
||||||
assert "bootstrap" in vocab # playbook identity
|
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():
|
def test_role_names_reads_role_dirs():
|
||||||
names = ct.role_names()
|
names = ct.role_names()
|
||||||
assert "base" in names
|
assert "base" in names
|
||||||
assert "docker_host" 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