"""Parse terraform/tofu/terragrunt plan output for resource actions.""" from __future__ import annotations import re from dataclasses import dataclass @dataclass(frozen=True) class ParsedResource: address: str type: str action: str _HEADER_RE = re.compile( r"^\s*#\s+(?P\S+)\s+(?:" r"(?Pwill be created)|" r"(?Pwill be updated in-place)|" r"(?Pwill be destroyed)|" r"(?Pmust be replaced|will be replaced)|" r"(?Pwill be read during apply)" r")\s*$" ) def _action_from_match(m: re.Match) -> str: for k in ("create", "update", "delete", "replace", "read"): if m.group(k): return k return "unknown" def _type_from_address(address: str) -> str: """Strip module prefixes and any index suffix; return the resource type.""" parts = address.split(".") i = 0 while i + 1 < len(parts) and parts[i].startswith("module"): i += 2 if i < len(parts) and parts[i] == "data": i += 1 if i >= len(parts): return "" type_token = parts[i] return re.sub(r"\[.*\]$", "", type_token) def parse_plan_resources(plan_stdout: str) -> list[ParsedResource]: out: list[ParsedResource] = [] for line in plan_stdout.splitlines(): m = _HEADER_RE.match(line) if not m: continue addr = m.group("addr") out.append(ParsedResource( address=addr, type=_type_from_address(addr), action=_action_from_match(m), )) return out