mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
An inventory group web-app-cst-<name> with applications.<id>.custom: true and
a pinned source {url, version, path} is pulled into roles/ before the play,
read raw (Jinja, Ansible tags and credential URLs refused), placed through a
staging directory under an exclusive lock, and owned by a marker that names
its own directory, so a pull or roles-clean never replaces or removes a
directory it did not place and a failed pull leaves roles/ as it was.
Directory inventories, host lists and non-UTF-8 files behave as Ansible
reads them; generated test deploys pass --skip-pull. make roles-pull,
roles-clean and roles list, INFINITO_EXTRA_MOUNTS for local sources, the
web-app-cst category, the reserved prefix lint and a central
repository_files() that also yields the gitignored custom roles let every
lint scan them like in-tree roles. Supersedes the uncommitted 045 External
Roles draft and reuses its fetch, sync and mount design.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
218 lines
6.2 KiB
Python
218 lines
6.2 KiB
Python
"""Unit tests for utils.inventory.groups."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import textwrap
|
|
import unittest
|
|
from pathlib import Path
|
|
from tempfile import TemporaryDirectory
|
|
|
|
from utils.inventory.groups import inventory_groups, inventory_has_group
|
|
|
|
|
|
class TestInventoryGroups(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.tmp = TemporaryDirectory()
|
|
self.addCleanup(self.tmp.cleanup)
|
|
self.root = Path(self.tmp.name)
|
|
|
|
def _write(self, name: str, body: str) -> str:
|
|
path = self.root / name
|
|
path.write_text(textwrap.dedent(body).lstrip("\n"), encoding="utf-8")
|
|
return str(path)
|
|
|
|
def test_yaml_lists_top_level_and_nested_children(self) -> None:
|
|
path = self._write(
|
|
"inv.yml",
|
|
"""
|
|
all:
|
|
children:
|
|
web-app-cst-store:
|
|
children:
|
|
nested-group:
|
|
hosts: {}
|
|
hosts:
|
|
localhost: {}
|
|
""",
|
|
)
|
|
self.assertEqual(
|
|
{"all", "web-app-cst-store", "nested-group"}, inventory_groups(path)
|
|
)
|
|
|
|
def test_yaml_hosts_and_vars_are_no_groups(self) -> None:
|
|
path = self._write(
|
|
"inv.yml",
|
|
"""
|
|
all:
|
|
hosts:
|
|
web-app-cst-host: {}
|
|
vars:
|
|
applications:
|
|
web-app-cst-store: {custom: true}
|
|
""",
|
|
)
|
|
self.assertEqual({"all"}, inventory_groups(path))
|
|
|
|
def test_ini_lists_sections_and_children(self) -> None:
|
|
path = self._write(
|
|
"hosts",
|
|
"""
|
|
# [comment-group]
|
|
[web-app-cst-store]
|
|
localhost
|
|
|
|
[parent:children]
|
|
child-group
|
|
|
|
[parent:vars]
|
|
key=value
|
|
""",
|
|
)
|
|
self.assertEqual(
|
|
{"web-app-cst-store", "parent", "child-group"}, inventory_groups(path)
|
|
)
|
|
|
|
def test_yaml_with_ansible_tags_lists_its_groups(self) -> None:
|
|
path = self._write(
|
|
"inv.yml",
|
|
"""
|
|
all:
|
|
hosts:
|
|
localhost:
|
|
motd: !unsafe '{{ literal }}'
|
|
secret: !vault |
|
|
$ANSIBLE_VAULT;1.1;AES256
|
|
6162
|
|
children:
|
|
web-app-cst-store:
|
|
hosts: {}
|
|
""",
|
|
)
|
|
self.assertEqual({"all", "web-app-cst-store"}, inventory_groups(path))
|
|
|
|
def test_missing_yaml_inventory_raises(self) -> None:
|
|
with self.assertRaises(FileNotFoundError):
|
|
inventory_groups(str(self.root / "absent.yml"))
|
|
|
|
|
|
class TestInventoryHasGroupYaml(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.tmp = TemporaryDirectory()
|
|
self.addCleanup(self.tmp.cleanup)
|
|
self.root = Path(self.tmp.name)
|
|
|
|
def _write(self, name: str, body: str) -> str:
|
|
path = self.root / name
|
|
path.write_text(textwrap.dedent(body).lstrip("\n"), encoding="utf-8")
|
|
return str(path)
|
|
|
|
def test_yaml_finds_nested_group(self) -> None:
|
|
path = self._write(
|
|
"inv.yml",
|
|
"""
|
|
all:
|
|
children:
|
|
svc-swarm-node:
|
|
hosts:
|
|
swarm-mgr-01: {}
|
|
svc-swarm-manager:
|
|
hosts:
|
|
swarm-mgr-01: {}
|
|
""",
|
|
)
|
|
self.assertTrue(inventory_has_group(path, "svc-swarm-manager"))
|
|
self.assertTrue(inventory_has_group(path, "svc-swarm-node"))
|
|
|
|
def test_yaml_missing_group_returns_false(self) -> None:
|
|
path = self._write(
|
|
"inv.yml",
|
|
"""
|
|
all:
|
|
children:
|
|
svc-swarm-node:
|
|
hosts:
|
|
swarm-mgr-01: {}
|
|
""",
|
|
)
|
|
self.assertFalse(inventory_has_group(path, "svc-swarm-manager"))
|
|
|
|
def test_yaml_skips_scalar_key(self) -> None:
|
|
path = self._write(
|
|
"inv.yml",
|
|
"""
|
|
all:
|
|
children:
|
|
some-key: just-a-string
|
|
actual-group:
|
|
hosts: {}
|
|
""",
|
|
)
|
|
self.assertFalse(inventory_has_group(path, "some-key"))
|
|
self.assertTrue(inventory_has_group(path, "actual-group"))
|
|
|
|
|
|
class TestInventoryHasGroupIni(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.tmp = TemporaryDirectory()
|
|
self.addCleanup(self.tmp.cleanup)
|
|
self.root = Path(self.tmp.name)
|
|
|
|
def _write(self, name: str, body: str) -> str:
|
|
path = self.root / name
|
|
path.write_text(textwrap.dedent(body).lstrip("\n"), encoding="utf-8")
|
|
return str(path)
|
|
|
|
def test_ini_finds_section_header(self) -> None:
|
|
path = self._write(
|
|
"hosts",
|
|
"""
|
|
[svc-swarm-manager]
|
|
mgr-01
|
|
|
|
[web-app-mediawiki]
|
|
mgr-01
|
|
""",
|
|
)
|
|
self.assertTrue(inventory_has_group(path, "svc-swarm-manager"))
|
|
self.assertTrue(inventory_has_group(path, "web-app-mediawiki"))
|
|
|
|
def test_ini_finds_host_in_section(self) -> None:
|
|
path = self._write(
|
|
"hosts",
|
|
"""
|
|
[grp]
|
|
host-a, host-b host-c
|
|
""",
|
|
)
|
|
self.assertTrue(inventory_has_group(path, "host-a"))
|
|
self.assertTrue(inventory_has_group(path, "host-b"))
|
|
self.assertTrue(inventory_has_group(path, "host-c"))
|
|
|
|
def test_ini_skips_comments(self) -> None:
|
|
path = self._write(
|
|
"hosts",
|
|
"""
|
|
# [comment-group]
|
|
; [also-comment]
|
|
[real-group]
|
|
mgr-01
|
|
""",
|
|
)
|
|
self.assertFalse(inventory_has_group(path, "comment-group"))
|
|
self.assertFalse(inventory_has_group(path, "also-comment"))
|
|
self.assertTrue(inventory_has_group(path, "real-group"))
|
|
|
|
def test_ini_missing_group_returns_false(self) -> None:
|
|
path = self._write(
|
|
"hosts",
|
|
"""
|
|
[other]
|
|
mgr-01
|
|
""",
|
|
)
|
|
self.assertFalse(inventory_has_group(path, "missing"))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|