"""Detect tool, run init + plan, capture output.""" from __future__ import annotations import re import subprocess from pathlib import Path from typing import Literal from scripts.manifest import InitResult, PlanResult Tool = Literal["terragrunt", "tofu"] _TAIL_BYTES = 4096 _SUMMARY_RE = re.compile( r"Plan:\s+(\d+\s+to add,\s*\d+\s+to change,\s*\d+\s+to destroy)\." ) def detect_tool(plan_dir: Path | str) -> Tool: p = Path(plan_dir) if (p / "terragrunt.hcl").is_file(): return "terragrunt" return "tofu" def _tail(s: str, n: int = _TAIL_BYTES) -> str: return s[-n:] if len(s) > n else s def extract_summary(plan_stdout: str) -> str: m = _SUMMARY_RE.search(plan_stdout) if m: return re.sub(r"\s+", " ", m.group(1)).strip() if "No changes." in plan_stdout or "no changes." in plan_stdout.lower(): return "no changes" return "unknown" def run_init(plan_dir: Path | str, tool: Tool) -> InitResult: args = [tool, "init", "-input=false", "-no-color"] result = subprocess.run( args, cwd=str(plan_dir), capture_output=True, text=True, check=False, ) return InitResult( ok=(result.returncode == 0), stdout_tail=_tail(result.stdout), stderr_tail=_tail(result.stderr), ) def run_plan(plan_dir: Path | str, tool: Tool, stdout_path: Path) -> PlanResult: args = [tool, "plan", "-input=false", "-no-color"] result = subprocess.run( args, cwd=str(plan_dir), capture_output=True, text=True, check=False, ) stdout_path.parent.mkdir(parents=True, exist_ok=True) stdout_path.write_text(result.stdout) return PlanResult( ok=(result.returncode == 0), stdout_path=str(stdout_path), exit_code=result.returncode, summary=extract_summary(result.stdout), )