Navegador

Blame History Raw 204 lines
1
"""
2
Chef framework enricher.
3
4
Promotes generic graph nodes created by the Ruby parser to Chef-specific
5
semantic types:
6
- chef_recipe — files under recipes/
7
- chef_cookbook — metadata.rb files under cookbooks/
8
- chef_resource — functions/methods in recipes/ or libraries/ matching
9
Chef resource names (package, template, service, etc.)
10
- include_recipe — DEPENDS_ON edges for cross-recipe includes
11
"""
12
13
from navegador.enrichment.base import EnrichmentResult, FrameworkEnricher
14
from navegador.graph.store import GraphStore
15
16
# Built-in Chef resource types that appear as method calls in recipes
17
_CHEF_RESOURCES = frozenset(
18
{
19
"package",
20
"template",
21
"service",
22
"execute",
23
"file",
24
"directory",
25
"cookbook_file",
26
"remote_file",
27
"cron",
28
"user",
29
"group",
30
"mount",
31
"link",
32
"bash",
33
"ruby_block",
34
"apt_package",
35
"yum_package",
36
"powershell_script",
37
"windows_service",
38
"chef_gem",
39
"log",
40
"http_request",
41
"remote_directory",
42
}
43
)
44
45
46
class ChefEnricher(FrameworkEnricher):
47
"""Enriches a navegador graph with Chef-specific semantic types."""
48
49
def __init__(self, store: GraphStore) -> None:
50
super().__init__(store)
51
52
# ── Identity ──────────────────────────────────────────────────────────────
53
54
@property
55
def framework_name(self) -> str:
56
return "chef"
57
58
@property
59
def detection_patterns(self) -> list[str]:
60
return ["chef"]
61
62
@property
63
def detection_files(self) -> list[str]:
64
return ["metadata.rb", "Berksfile"]
65
66
# ── Enrichment ────────────────────────────────────────────────────────────
67
68
def enrich(self) -> EnrichmentResult:
69
result = EnrichmentResult()
70
71
recipes = self._enrich_recipes()
72
result.promoted += recipes
73
result.patterns_found["recipes"] = recipes
74
75
cookbooks = self._enrich_cookbooks()
76
result.promoted += cookbooks
77
result.patterns_found["cookbooks"] = cookbooks
78
79
resources = self._enrich_resources()
80
result.promoted += resources
81
result.patterns_found["resources"] = resources
82
83
includes = self._enrich_include_recipe()
84
result.edges_added += includes
85
result.patterns_found["include_recipe"] = includes
86
87
return result
88
89
# ── Pattern helpers ───────────────────────────────────────────────────────
90
91
def _enrich_recipes(self) -> int:
92
"""Promote File nodes under /recipes/ to chef_recipe."""
93
promoted = 0
94
query_result = self.store.query(
95
"MATCH (n:File) WHERE n.file_path CONTAINS $pattern RETURN n.name, n.file_path",
96
{"pattern": "/recipes/"},
97
)
98
rows = query_result.result_set or []
99
for row in rows:
100
name, file_path = row[0], row[1]
101
if name and file_path:
102
self._promote_node(name, file_path, "chef_recipe")
103
promoted += 1
104
return promoted
105
106
def _enrich_cookbooks(self) -> int:
107
"""Promote metadata.rb File nodes under /cookbooks/ to chef_cookbook."""
108
promoted = 0
109
query_result = self.store.query(
110
"MATCH (n:File) WHERE n.file_path CONTAINS $cookbooks "
111
"AND n.name = $name "
112
"RETURN n.name, n.file_path",
113
{"cookbooks": "/cookbooks/", "name": "metadata.rb"},
114
)
115
rows = query_result.result_set or []
116
for row in rows:
117
name, file_path = row[0], row[1]
118
if name and file_path:
119
self._promote_node(name, file_path, "chef_cookbook")
120
promoted += 1
121
return promoted
122
123
def _enrich_resources(self) -> int:
124
"""Promote Function/Method nodes in recipes/ or libraries/ whose names
125
match Chef built-in resource types."""
126
promoted = 0
127
for path_fragment in ("/recipes/", "/libraries/"):
128
query_result = self.store.query(
129
"MATCH (n) WHERE (n:Function OR n:Method) "
130
"AND n.file_path CONTAINS $pattern "
131
"RETURN n.name, n.file_path",
132
{"pattern": path_fragment},
133
)
134
rows = query_result.result_set or []
135
for row in rows:
136
name, file_path = row[0], row[1]
137
if name and file_path and name in _CHEF_RESOURCES:
138
self._promote_node(name, file_path, "chef_resource")
139
promoted += 1
140
return promoted
141
142
def _enrich_include_recipe(self) -> int:
143
"""Link include_recipe calls to the referenced recipe File nodes.
144
145
Looks for Function nodes named ``include_recipe`` and follows CALLS
146
edges or checks node properties to find the recipe name argument,
147
then creates a DEPENDS_ON edge to the matching recipe File node.
148
"""
149
edges_added = 0
150
151
# Strategy 1: follow CALLS edges from include_recipe nodes
152
query_result = self.store.query(
153
"MATCH (n:Function)-[:CALLS]->(target) "
154
"WHERE n.name = $name "
155
"RETURN n.file_path, target.name",
156
{"name": "include_recipe"},
157
)
158
rows = query_result.result_set or []
159
for row in rows:
160
caller_path, recipe_ref = row[0], row[1]
161
if caller_path and recipe_ref:
162
# recipe_ref may be "cookbook::recipe" — extract recipe name
163
recipe_name = recipe_ref.split("::")[-1] if "::" in recipe_ref else recipe_ref
164
# Find the recipe File node
165
match_result = self.store.query(
166
"MATCH (f:File) WHERE f.file_path CONTAINS $recipes "
167
"AND f.name CONTAINS $recipe "
168
"RETURN f.name",
169
{"recipes": "/recipes/", "recipe": recipe_name},
170
)
171
match_rows = match_result.result_set or []
172
if match_rows and match_rows[0][0]:
173
# Create DEPENDS_ON from the caller's file to the recipe file
174
caller_file_result = self.store.query(
175
"MATCH (f:File) WHERE f.file_path = $path RETURN f.name",
176
{"path": caller_path},
177
)
178
caller_rows = caller_file_result.result_set or []
179
if caller_rows and caller_rows[0][0]:
180
self._add_semantic_edge(
181
caller_rows[0][0],
182
"DEPENDS_ON",
183
match_rows[0][0],
184
)
185
edges_added += 1
186
187
# Strategy 2: check signature/docstring for include_recipe calls
188
for prop in ("signature", "docstring"):
189
query_result = self.store.query(
190
f"MATCH (n) WHERE (n:Function OR n:Method) "
191
f"AND n.{prop} IS NOT NULL "
192
f"AND n.{prop} CONTAINS $pattern "
193
"RETURN n.name, n.file_path",
194
{"pattern": "include_recipe"},
195
)
196
rows = query_result.result_set or []
197
for row in rows:
198
name, file_path = row[0], row[1]
199
if name and file_path and name == "include_recipe":
200
# Already handled in strategy 1 via CALLS edges
201
continue
202
203
return edges_added
204

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button