Terraform Language Parser.
Handles parsing of Terraform configuration files (.tf) and JSON plans.
Uses specialized extractors for resources, variables, outputs, and modules.
Bases: LanguageParser
Static analysis parser for Terraform HCL files.
Source code in src/jnkn/parsing/terraform/parser.py
| class TerraformParser(LanguageParser):
"""
Static analysis parser for Terraform HCL files.
"""
def __init__(self, context: ParserContext | None = None):
super().__init__(context)
self._extractors = ExtractorRegistry()
self._register_extractors()
def _register_extractors(self) -> None:
"""Register the standard suite of Terraform extractors."""
self._extractors.register(ResourceExtractor())
self._extractors.register(OutputExtractor())
self._extractors.register(VariableExtractor())
self._extractors.register(ModuleExtractor())
self._extractors.register(DataSourceExtractor())
self._extractors.register(LocalsExtractor())
self._extractors.register(ReferenceExtractor())
@property
def name(self) -> str:
return "terraform"
@property
def extensions(self) -> List[str]:
return [".tf"]
def can_parse(self, file_path: Path, content: bytes | None = None) -> bool:
return file_path.suffix == ".tf"
def parse(self, file_path: Path, content: bytes) -> Generator[Union[Node, Edge], None, None]:
try:
text = content.decode(self.context.encoding)
except UnicodeDecodeError:
return
file_id = f"file://{file_path}"
# Create the file node
yield Node(
id=file_id,
name=file_path.name,
type=NodeType.CODE_FILE,
path=str(file_path),
metadata={"language": "hcl"},
)
# Execute extractors
ctx = ExtractionContext(file_path=file_path, file_id=file_id, text=text, seen_ids=set())
yield from self._extractors.extract_all(ctx)
|
Bases: LanguageParser
Parser for Terraform JSON plan output (tfplan.json).
Source code in src/jnkn/parsing/terraform/parser.py
| class TerraformPlanParser(LanguageParser):
"""
Parser for Terraform JSON plan output (tfplan.json).
"""
@property
def name(self) -> str:
return "terraform_plan"
@property
def extensions(self) -> List[str]:
return [".json"]
def can_parse(self, file_path: Path, content: bytes | None = None) -> bool:
"""
Check if file is a Terraform plan JSON.
Uses heuristics on filename and content structure.
"""
if file_path.suffix == ".json" and "plan" in file_path.name.lower():
return True
if content:
try:
start = content[:200].decode("utf-8", errors="ignore")
return "resource_changes" in start or "terraform_version" in start
except Exception:
pass
return False
def parse(self, file_path: Path, content: bytes) -> List[Union[Node, Edge]]:
results = []
try:
plan = json.loads(content)
except json.JSONDecodeError:
return []
if "resource_changes" not in plan:
return []
for change in plan["resource_changes"]:
res_type = change.get("type")
res_name = change.get("name")
address = change.get("address")
if not res_type or not res_name:
continue
node_id = f"infra:{res_type}.{res_name}"
node = Node(
id=node_id,
name=res_name,
type=NodeType.INFRA_RESOURCE,
metadata={
"terraform_address": address,
"change_actions": change.get("change", {}).get("actions", []),
},
)
results.append(node)
return results
|
Check if file is a Terraform plan JSON.
Uses heuristics on filename and content structure.
Source code in src/jnkn/parsing/terraform/parser.py
| def can_parse(self, file_path: Path, content: bytes | None = None) -> bool:
"""
Check if file is a Terraform plan JSON.
Uses heuristics on filename and content structure.
"""
if file_path.suffix == ".json" and "plan" in file_path.name.lower():
return True
if content:
try:
start = content[:200].decode("utf-8", errors="ignore")
return "resource_changes" in start or "terraform_version" in start
except Exception:
pass
return False
|