mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-09 12:17:13 +00:00
The Cosmos section drew one diagram inside the README and truncated its
dependents at twelve. It is replaced by an `ARCHITECTURE.md` sub-page per
invokable role with four sections, and the README keeps only a link table.
Compose lists the containers with image and published ports. Swarm shows the
replica count and the effective placement constraint per service. Volumes
draws the decision chain `swarm_nfs_backed` walks instead of its outcome,
because whether a volume is NFS-backed depends on the mode, the storage
backend, the entry's own `nfs:` flag and the role's placement at once, and a
diagram stating only the result hides the coupling that makes a placement
change move data silently. Dependencies is the cosmos graph with the cap
removed, so `_DEPENDENTS_CAP`, `shown`, `overflow` and the `dpt_more` node are
gone.
Which containers a role runs cannot be read off `meta/services.yml`: it mixes
the role's own services with the ones it merely consumes, and the entries that
do describe a container carry `name`, or only `image`, or neither when the
image is built, so no field discriminates. A heuristic over that file dropped
`pds` from web-app-bluesky, five of six services from web-app-funkwhale and
the only service of web-svc-cdn. The new `services` module reads the
assignments that actually render a container, `{% set service_name = ... %}`,
resolving literals directly and constants through `vars/main.yml`; every
template in the tree that includes the container base carries one.
The page is written by its own unconditional loop in `__main__.py`, not gated
behind `--update-architecture`, so the documentation build cannot emit the
link table without the page it points at. `--check` compares instead of
writing. A committed copy is refused by
`test_no_role_commits_the_architecture_sub_page`, the rule the managed README
sections already follow.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
121 lines
4.0 KiB
Python
Executable File
121 lines
4.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Generate or complete role README.md files from the schema template.
|
|
|
|
Usage:
|
|
python -m cli.build.docs.readme [roles...] [--override] [--check] [--roles-dir DIR]
|
|
|
|
With no role names every role directory is processed. ``--override``
|
|
regenerates the managed sections (Architecture, Quick Setup, Credits) even
|
|
when they already exist; without it, only missing sections are added.
|
|
``--update-architecture`` regenerates only the Architecture section. Prose
|
|
sections are never rewritten. Every invokable role additionally gets an
|
|
``ARCHITECTURE.md`` sub-page. ``--check`` writes nothing and exits non-zero
|
|
when any file would change (for CI).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from pathlib import Path
|
|
|
|
from cli.build.docs.readme.generate import (
|
|
MANAGED_SECTIONS,
|
|
generate_architecture_page,
|
|
generate_readme,
|
|
role_dirs,
|
|
)
|
|
from utils.cache.files import PROJECT_ROOT, read_text
|
|
from utils.roles.mapping import ROLE_FILE_ARCHITECTURE, ROLE_FILE_README
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Generate role README.md files.")
|
|
parser.add_argument("roles", nargs="*", help="Role names (default: all).")
|
|
parser.add_argument(
|
|
"--override",
|
|
action="store_true",
|
|
help="Regenerate managed sections even when present (default: only add missing).",
|
|
)
|
|
parser.add_argument(
|
|
"--update-architecture",
|
|
action="store_true",
|
|
help="Regenerate only the Architecture section (implies override for it).",
|
|
)
|
|
parser.add_argument(
|
|
"--update-quick-setup",
|
|
action="store_true",
|
|
help="Regenerate only the Quick Setup section (implies override for it).",
|
|
)
|
|
parser.add_argument(
|
|
"--check",
|
|
action="store_true",
|
|
help="Do not write; exit non-zero if any README would change.",
|
|
)
|
|
parser.add_argument(
|
|
"--roles-dir",
|
|
default=str(PROJECT_ROOT / "roles"),
|
|
help="Directory containing role subfolders.",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
roles_root = Path(args.roles_dir)
|
|
if not roles_root.is_dir():
|
|
parser.error(f"Roles directory not found: {roles_root}")
|
|
|
|
if args.roles:
|
|
targets = [roles_root / name for name in args.roles]
|
|
missing = [str(p) for p in targets if not p.is_dir()]
|
|
if missing:
|
|
parser.error("Unknown role(s): " + ", ".join(missing))
|
|
else:
|
|
targets = role_dirs(roles_root)
|
|
|
|
if args.update_architecture:
|
|
only, override = ("Architecture",), True
|
|
elif args.update_quick_setup:
|
|
only, override = ("Quick Setup",), True
|
|
else:
|
|
only, override = MANAGED_SECTIONS, args.override
|
|
|
|
changed = 0
|
|
for role_dir in targets:
|
|
if (
|
|
not (role_dir / ROLE_FILE_README).is_file()
|
|
and not (role_dir / "meta").is_dir()
|
|
):
|
|
continue
|
|
new_text, actions = generate_readme(
|
|
role_dir, role_dir.name, override=override, only=only
|
|
)
|
|
if new_text is None:
|
|
continue
|
|
changed += 1
|
|
if args.check:
|
|
print(f"would update roles/{role_dir.name}/README.md: {', '.join(actions)}")
|
|
continue
|
|
(role_dir / ROLE_FILE_README).write_text(new_text, encoding="utf-8")
|
|
print(f"updated roles/{role_dir.name}/README.md: {', '.join(actions)}")
|
|
|
|
for role_dir in targets:
|
|
page = generate_architecture_page(role_dir, role_dir.name)
|
|
if page is None:
|
|
continue
|
|
path = role_dir / ROLE_FILE_ARCHITECTURE
|
|
if args.check:
|
|
if not path.is_file() or read_text(str(path)) != page:
|
|
print(f"would write roles/{role_dir.name}/{ROLE_FILE_ARCHITECTURE}")
|
|
changed += 1
|
|
continue
|
|
path.write_text(page, encoding="utf-8")
|
|
|
|
if args.check and changed:
|
|
print(f"\n{changed} file(s) would change.")
|
|
return 1
|
|
if not args.check:
|
|
print(f"\n{changed} file(s) written.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|