mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
fix(deploy): let init pin the guide host the way deploy already does
adb53392bdtaught the deploy planner to pin the instructions host to its smallest variant and to leave a pinned app out of the round total. It taught only the deploy side. `init` writes the inventory and `deploy` reads it back, and the directory carries a round suffix only when the plan holds more than one round, so the two sides named different directories: >>> Inventory initialized at /home/runner/inventories/github-0 === matrix-deploy: round 1/1 inv=/home/runner/inventories/github [ERROR] Some application_ids are invalid for this inventory [DEBUG] inventory file does not exist Nine jobs of run 36596159856 died there, before ansible-playbook started. The trigger is a subject with a single variant: init counted the host's two variants and suffixed, deploy pinned the host out and did not. svc-ai-s1 declares two variants of its own and was the only instructions row to survive, which is the shape of the bug read backwards. `variant_select` opens by stating the invariant this broke - "keeps init and deploy walking the exact same selection (SPOT)" - so the fix is to honour it rather than to special-case the suffix. The accompanying test asserts both entry points forward `pinned_variants`. The five tests that shipped withadb53392bdall exercised the pin helper on its own, which is why nothing caught a caller that never asked it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
adb53392bd
commit
3b85e4e1e1
2 files changed
+72
-2
No files matched your search
@@ -21,7 +21,11 @@ from .inventory import (
|
||||
_build_services_overrides_for_round as build_services_overrides_for_round,
|
||||
)
|
||||
from .storage import detect_storage_constrained
|
||||
from .variant_select import add_variant_args, apply_variant_filter
|
||||
from .variant_select import (
|
||||
add_variant_args,
|
||||
apply_variant_filter,
|
||||
env_guide_host_pin,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import argparse
|
||||
@@ -112,10 +116,12 @@ def handler(args: argparse.Namespace) -> int:
|
||||
else args.force_storage_constrained == "true"
|
||||
)
|
||||
|
||||
roles_dir = str(compose.repo_root / "roles")
|
||||
plan = plan_dev_inventory_matrix(
|
||||
roles_dir=str(compose.repo_root / "roles"),
|
||||
roles_dir=roles_dir,
|
||||
primary_apps=primary_apps,
|
||||
base_inventory_dir=str(args.inventory_dir),
|
||||
pinned_variants=env_guide_host_pin(roles_dir, primary_apps),
|
||||
)
|
||||
try:
|
||||
plan = apply_variant_filter(plan, args)
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
"""``init`` and ``deploy`` must hand the planner the same variant pins.
|
||||
|
||||
``init`` writes the inventory and ``deploy`` reads it back. The directory name
|
||||
carries a round suffix only when the plan has more than one round, so a pin
|
||||
that one of them applies and the other does not renames the directory under
|
||||
the other's feet: init built ``<dir>-0`` while deploy looked for ``<dir>`` and
|
||||
every app came back "not present in inventory".
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from . import PROJECT_ROOT
|
||||
|
||||
PLANNER = "plan_dev_inventory_matrix"
|
||||
PIN = "pinned_variants"
|
||||
|
||||
CALLERS = (
|
||||
"cli/administration/deploy/development/init.py",
|
||||
"cli/administration/deploy/development/deploy/cli.py",
|
||||
)
|
||||
|
||||
|
||||
def pins_at(source: str) -> list[bool]:
|
||||
"""Whether each planner call in *source* passes the pins.
|
||||
|
||||
Args:
|
||||
source: the module's text.
|
||||
"""
|
||||
tree = ast.parse(source)
|
||||
found: list[bool] = []
|
||||
for node in ast.walk(tree):
|
||||
if not isinstance(node, ast.Call):
|
||||
continue
|
||||
name = node.func.id if isinstance(node.func, ast.Name) else None
|
||||
if name != PLANNER:
|
||||
continue
|
||||
found.append(any(kw.arg == PIN for kw in node.keywords))
|
||||
return found
|
||||
|
||||
|
||||
class TestInitAndDeployPlanAlike(unittest.TestCase):
|
||||
def test_both_entry_points_pass_the_variant_pins(self) -> None:
|
||||
missing: list[str] = []
|
||||
for rel in CALLERS:
|
||||
path = Path(PROJECT_ROOT) / rel
|
||||
calls = pins_at(path.read_text(encoding="utf-8"))
|
||||
self.assertTrue(calls, f"{rel}: no {PLANNER} call found")
|
||||
missing.extend(rel for passes in calls if not passes)
|
||||
|
||||
self.assertEqual(
|
||||
missing,
|
||||
[],
|
||||
f"These call {PLANNER} without {PIN}, so they plan a different "
|
||||
"number of rounds than their counterpart and disagree on the "
|
||||
f"inventory directory: {missing}",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user