66from rdflib import Graph
77from rdflib .namespace import RDF , RDFS
88
9- from d3fendtools .kuberdf import NS_D3F , NS_K8S
9+ from d3fendtools .kuberdf import D3F , K8S
1010
1111log = logging .getLogger (__name__ )
1212
@@ -23,6 +23,76 @@ def render_in_chunks(items, chunk_size=6):
2323 yield "end"
2424
2525
26+ RENDER_MAP = {
27+ K8S .Container : {
28+ "shape" : "process" ,
29+ },
30+ K8S .Selector : {
31+ "shape" : "odd" ,
32+ },
33+ K8S .Service : {
34+ "shape" : "circle" ,
35+ },
36+ K8S .ServiceAccount : {
37+ "shape" : "circle" ,
38+ },
39+ K8S .User : {
40+ "shape" : "circle" ,
41+ },
42+ K8S .Image : {
43+ "shape" : "lin-cyl" ,
44+ },
45+ K8S .ImageStream : {
46+ "shape" : "lin-cyl" ,
47+ },
48+ K8S .ImageStreamTag : {
49+ "shape" : "lin-cyl" ,
50+ },
51+ K8S .PersistentVolumeClaim : {
52+ "shape" : "lin-cyl" ,
53+ },
54+ K8S .Volume : {
55+ "shape" : "lin-cyl" ,
56+ },
57+ K8S .Route : {
58+ "shape" : "delay" ,
59+ },
60+ K8S .Deployment : {
61+ "shape" : "processes" ,
62+ },
63+ K8S .DeploymentConfig : {
64+ "shape" : "processes" ,
65+ },
66+ K8S .ConfigMap : {
67+ "shape" : "doc" ,
68+ },
69+ K8S .Secret : {
70+ "shape" : "doc" ,
71+ },
72+ K8S .Endpoints : {
73+ "shape" : "curv-trap" ,
74+ },
75+ K8S .Host : {
76+ "shape" : "trap-b" ,
77+ },
78+ K8S .HorizontalPodAutoscaler : {
79+ "shape" : "subproc" ,
80+ },
81+ K8S .RoleBinding : {
82+ "shape" : "doc" ,
83+ },
84+ K8S .DeveloperAccount : {
85+ "shape" : "circle" ,
86+ },
87+ K8S .DeveloperUser : {
88+ "shape" : "circle" ,
89+ },
90+ K8S .ExternalSecret : {
91+ "shape" : "process" ,
92+ },
93+ }
94+
95+
2696class RDF2Mermaid :
2797 """Convert an RDF graph to a mermaid graph."""
2898
@@ -47,15 +117,17 @@ class RDF2Mermaid:
47117 "urn:k8s:Port" : "fa:fa-ethernet" ,
48118 "urn:k8s:Secret" : "fa:fa-key" ,
49119 "urn:k8s:Service" : "fa:fa-sitemap" ,
120+ "urn:k8s:ServiceAccount" : "fa:fa-user-shield" ,
50121 "urn:k8s:Registry" : "fab:fa-docker fa:fa-boxes-stacked" ,
51122 "urn:k8s:Route" : "fa:fa-route" ,
123+ "urn:k8s:RoleBinding" : "fa:fa-id-badge" ,
52124 }
53125 # NB: A node connected with another is always rendered.
54126 DONT_RENDER = (
55127 # These are not nodes.
56- NS_K8S .Pod ,
57- NS_K8S .Job ,
58- NS_K8S .BuildConfig ,
128+ K8S .Pod ,
129+ K8S .Job ,
130+ K8S .BuildConfig ,
59131 # NS_K8S.Selector,
60132 # NS_K8S.Host,
61133 # # For now, skip these. XXX
@@ -64,20 +136,20 @@ class RDF2Mermaid:
64136 # NS_K8S.ImageStreamTag,
65137 )
66138 RENDER_AS_SUBGRAPHS = (
67- NS_K8S .Namespace ,
68- NS_K8S .Registry ,
69- NS_K8S .Application ,
139+ K8S .Namespace ,
140+ K8S .Registry ,
141+ K8S .Application ,
70142 # DC are groups.
71- NS_K8S .DeploymentConfig ,
72- NS_K8S .Deployment ,
73- NS_K8S .StatefulSet ,
143+ K8S .DeploymentConfig ,
144+ K8S .Deployment ,
145+ K8S .StatefulSet ,
74146 )
75147 DONT_RENDER_AS_NODES = DONT_RENDER + RENDER_AS_SUBGRAPHS
76148
77149 def __init__ (self , g : Graph ):
78150 self .g = g
79- g .bind ("k8s" , NS_K8S )
80- g .bind ("d3f" , NS_D3F )
151+ g .bind ("k8s" , K8S )
152+ g .bind ("d3f" , D3F )
81153 self .lines = []
82154 self .nodes = []
83155 self .edges = []
@@ -110,6 +182,16 @@ def sanitize_label(label):
110182 offset += 20
111183 return ret .strip (r"\n" ).strip ().replace ("=" , "_" )
112184
185+ def _format_link (self , link ):
186+ import re
187+
188+ def _replace_curie (match : str | re .Match ):
189+ needle = match .group (0 ) if isinstance (match , re .Match ) else match
190+ return self .g .namespace_manager .curie (needle )
191+
192+ ret = re .sub (r"http[^ ]+" , _replace_curie , link )
193+ return ret
194+
113195 def parse (self , match : str = "" , simplified_view = False ):
114196 if self .lines :
115197 log .info (f"Already parsed { len (self .lines )} lines." )
@@ -153,7 +235,7 @@ def parse(self, match: str = "", simplified_view=False):
153235
154236 # Create a tree of subgraph based on the
155237 # hasChild predicate.
156- for child in self .g .objects (s , NS_K8S .hasChild ) or []:
238+ for child in self .g .objects (s , K8S .hasChild ) or []:
157239 child = RDF2Mermaid .sanitize_uri (child )
158240 children_ = self .subgraphs .setdefault (
159241 src ,
@@ -165,7 +247,7 @@ def parse(self, match: str = "", simplified_view=False):
165247 )["children" ]
166248 children_ .append (child )
167249 children_ = None
168- if False and type_ == NS_K8S .Namespace and child .startswith ("TCP:__" ):
250+ if False and type_ == K8S .Namespace and child .startswith ("TCP:__" ):
169251 controlplane = self .subgraphs .setdefault (
170252 f"{ src } _cp" ,
171253 {
@@ -182,36 +264,8 @@ def parse(self, match: str = "", simplified_view=False):
182264 if type_ in RDF2Mermaid .DONT_RENDER_AS_NODES :
183265 log .warning ("Skipping %s" , s )
184266 else :
185- if type_ == NS_K8S .Container :
186- shape = "process"
187- elif type_ == NS_K8S .Selector :
188- shape = "odd"
189- elif type_ == NS_K8S .Service :
190- shape = "circle"
191- elif type_ in (NS_K8S .ServiceAccount , NS_K8S .User ):
192- shape = "circle"
193- elif type_ in (
194- NS_K8S .Image ,
195- NS_K8S .ImageStream ,
196- NS_K8S .ImageStreamTag ,
197- ):
198- shape = "database"
199- elif type_ in (NS_K8S .PersistentVolumeClaim , NS_K8S .Volume ):
200- shape = "lin-cyl"
201- elif type_ in (NS_K8S .Route ,):
202- shape = "delay"
203- elif type_ in (NS_K8S .Deployment , NS_K8S .DeploymentConfig ):
204- shape = "processes"
205- elif type_ in (NS_K8S .ConfigMap , NS_K8S .Secret ):
206- shape = "doc"
207- elif type_ in (NS_K8S .Endpoints ,):
208- shape = "curv-trap"
209- elif type_ in (NS_K8S .Host ,):
210- shape = "trap-b"
211- elif type_ in (NS_K8S .HorizontalPodAutoscaler ,):
212- shape = "subproc"
213- else :
214- shape = ""
267+ shape = RENDER_MAP .get (type_ , {}).get ("shape" , "" )
268+
215269 if shape :
216270 shape = '@{shape: "' + shape + '"}'
217271 line = f"""{ src } [{ label_l } ]{ shape } """ .replace ("\n " , "" )
@@ -222,18 +276,25 @@ def parse(self, match: str = "", simplified_view=False):
222276 # Adjust link direction to improve readability.
223277 #
224278 for link , arrow in (
225- (NS_K8S .exposes , "-.-o" ),
226- (NS_D3F .executes , "-->" ),
227- (NS_D3F .runs , "-->" ),
228- (NS_D3F .accesses , "-->" ),
229- (NS_D3F .reads , "-->" ),
279+ (K8S .exposes , "-.-o" ),
280+ (D3F .executes , "==>" ),
281+ (D3F .runs , "==>" ),
282+ (D3F .accesses , "-->" ),
283+ (D3F .reads , "-.-o" ),
284+ (D3F .creates , "-->" ),
285+ (D3F .authorizes , "-.-o" ),
230286 ):
231287 for dst in self .g .objects (s , link ) or []:
232288 dst = RDF2Mermaid .sanitize_uri (dst )
233- if link == NS_K8S .exposes :
289+ if link == K8S .exposes :
234290 src , dst = dst , src
235291 link = "exposed by"
236- elif link in (NS_D3F .executes , NS_D3F .runs ):
292+ elif link == D3F .reads :
293+ line = f"""{ dst } ~~~ { dst } """
294+ if line not in self .edges :
295+ self .edges .append (line )
296+
297+ elif link in (D3F .executes , D3F .runs ):
237298 pass
238299 # src, dst = dst, src
239300 # link = "executed by"
@@ -244,10 +305,10 @@ def parse(self, match: str = "", simplified_view=False):
244305 if link .startswith ("urn:k8s:" ):
245306 link = str (link )[8 :]
246307
247- if hasattr (link , "fragment" ):
248- link = link .fragment
308+ # if hasattr(link, "fragment"):
309+ # link = link.fragment
249310
250- line = f"""{ src } { arrow } |{ link } | { dst } """
311+ line = f"""{ src } { arrow } |{ self . _format_link ( link ) } | { dst } """
251312 if line not in self .edges :
252313 self .edges .append (line )
253314 log .info ("Parsed %s lines." , len (self .nodes ) + len (self .edges ))
@@ -257,12 +318,14 @@ def render(self):
257318 self .parse ()
258319
259320 ret = "graph LR\n "
260- ret += textwrap .dedent ("""
321+ ret += textwrap .dedent (
322+ """
261323 %% Style
262324 classDef namespace fill:none, stroke-dasharray: 5 5, stroke-width: 5px;
263325 classDef workload fill:none, stroke: blue;
264326 classDef network fill:none, stroke: green;
265- """ )
327+ """
328+ )
266329 ret += "\n " .join (sorted (self .nodes ) + sorted (self .edges ))
267330 ret += "\n "
268331 ret += "%%\n %% Subgraphs\n %%\n "
@@ -289,7 +352,7 @@ def _render_tree(parent, data):
289352 continue
290353 if child == parent :
291354 continue
292- if parent_type == NS_K8S .Namespace :
355+ if parent_type == K8S .Namespace :
293356 # Namespace should be processed last.
294357 # Deployment* and Service* should be processed
295358 # under Application.
@@ -299,7 +362,7 @@ def _render_tree(parent, data):
299362 continue
300363 if "_Service_" in child :
301364 continue
302- if parent_type == NS_K8S .Application :
365+ if parent_type == K8S .Application :
303366 # Application should be processed last.
304367 # Deployment* and Service* should be processed
305368 # under Application.
@@ -320,22 +383,22 @@ def _render_tree(parent, data):
320383 # children_to_render = render_in_chunks(children_to_render, 6)
321384 yield from children_to_render
322385 yield "end"
323- if parent_type == NS_K8S .Namespace :
386+ if parent_type == K8S .Namespace :
324387 yield f"class { parent } namespace;"
325388 elif parent_type in (
326- NS_K8S .Application ,
327- NS_K8S .DeploymentConfig ,
328- NS_K8S .Deployment ,
329- NS_K8S .StatefulSet ,
389+ K8S .Application ,
390+ K8S .DeploymentConfig ,
391+ K8S .Deployment ,
392+ K8S .StatefulSet ,
330393 ):
331394 yield f"class { parent } workload;"
332- elif parent_type in (NS_K8S .Service , NS_K8S .Endpoints ):
395+ elif parent_type in (K8S .Service , K8S .Endpoints ):
333396 yield f"class { parent } network;"
334397
335398 tree_namespace = []
336399 for parent , data in tree .items ():
337400 parent_type = data .get ("type" )
338- if parent_type == NS_K8S .Namespace :
401+ if parent_type == K8S .Namespace :
339402 tree_namespace .append ((parent , data ))
340403 continue
341404 yield from _render_tree (parent , data )
0 commit comments