Coverage for archdocs/features/kubernetes/manifest_files.py: 100%
43 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-18 22:03 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-18 22:03 +0000
1import pathlib
2import re as py_re
3import typing
5from archdocs import settings
6from archdocs.features.kubernetes import const
9_API_VERSION_PATTERN: typing.Final = py_re.compile(r"^apiVersion:", flags=settings.TYPICAL_RE_FLAGS)
10_KIND_PATTERN: typing.Final = py_re.compile(r"^kind:", flags=settings.TYPICAL_RE_FLAGS)
13def _read_manifest_text(one_file_path: pathlib.Path, /) -> str:
14 # Mirrors `main._read_source_text`: manifests are hunted through somebody's whole tree and
15 # a couple of directories above it, so a dangling symlink or a file the process may not open
16 # costs that one file — not the whole page.
17 try:
18 return one_file_path.read_text(errors="ignore")
19 except OSError:
20 return ""
23def _is_manifest_file(one_file_path: pathlib.Path, /) -> bool:
24 if one_file_path.stem == const.VALUES_FILE_STEM:
25 return True
26 file_source: typing.Final = _read_manifest_text(one_file_path)
27 return bool(_API_VERSION_PATTERN.search(file_source) and _KIND_PATTERN.search(file_source))
30def _find_manifest_files(search_dir: pathlib.Path, /) -> list[pathlib.Path]:
31 all_found_files: typing.Final = (
32 one_found_file
33 for one_file_suffix in const.MANIFEST_FILE_SUFFIXES
34 for one_found_file in search_dir.rglob(f"*{one_file_suffix}")
35 if settings.SKIPPED_DIR_NAMES.isdisjoint(one_found_file.relative_to(search_dir).parts)
36 )
37 return sorted(
38 (one_found_file for one_found_file in all_found_files if _is_manifest_file(one_found_file)),
39 key=lambda one_found_file: (len(one_found_file.parts), one_found_file),
40 )
43def _iter_search_roots(root_path: pathlib.Path, /) -> typing.Iterator[pathlib.Path]:
44 # Charts live next to the sources at least as often as inside them, so the search climbs a
45 # couple of levels up — but never out of the repository the sources belong to.
46 for one_search_root in (root_path, *list(root_path.parents)[: const.PARENT_SEARCH_DEPTH]):
47 yield one_search_root
48 if (one_search_root / const.REPOSITORY_MARKER_NAME).exists():
49 return
52def _find_manifest_dir(search_dir: pathlib.Path, /) -> pathlib.Path | None:
53 all_manifest_files: typing.Final = _find_manifest_files(search_dir)
54 if not all_manifest_files:
55 return None
56 closest_manifest_dir: typing.Final = all_manifest_files[0].parent
57 if closest_manifest_dir.name == const.TEMPLATES_DIR_NAME:
58 return closest_manifest_dir.parent
59 return closest_manifest_dir
62def _resolve_manifest_dir(root_path: pathlib.Path, configured_dir: str | pathlib.Path | None, /) -> pathlib.Path | None:
63 if configured_dir is None:
64 return next(
65 (
66 one_found_dir
67 for one_found_dir in map(_find_manifest_dir, _iter_search_roots(root_path))
68 if one_found_dir is not None
69 ),
70 None,
71 )
72 all_candidate_dirs: typing.Final = (
73 (one_search_root / configured_dir).resolve() for one_search_root in _iter_search_roots(root_path)
74 )
75 return next((one_candidate_dir for one_candidate_dir in all_candidate_dirs if one_candidate_dir.is_dir()), None)
78def read_kubernetes_manifests(root_path: pathlib.Path, configured_dir: str | pathlib.Path | None, /) -> str:
79 manifest_dir: typing.Final = _resolve_manifest_dir(root_path, configured_dir)
80 if manifest_dir is None:
81 return ""
82 return "".join(
83 f"{_read_manifest_text(one_manifest_file)}\n" for one_manifest_file in _find_manifest_files(manifest_dir)
84 )