Skip to content

Commit f94a4dc

Browse files
authored
Fix: #10. Add Rolebinding (#11)
* Add Rolebinding * Use render map. * Format links * Read link. * Fix read link orientation. * Add OCP templates.
1 parent ce0f99b commit f94a4dc

14 files changed

Lines changed: 1093 additions & 749 deletions

File tree

d3fendtools/as_mermaid/__init__.py

Lines changed: 128 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from rdflib import Graph
77
from rdflib.namespace import RDF, RDFS
88

9-
from d3fendtools.kuberdf import NS_D3F, NS_K8S
9+
from d3fendtools.kuberdf import D3F, K8S
1010

1111
log = 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+
2696
class 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)

d3fendtools/as_mermaid/utils.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from rdflib import Graph, RDF, OWL
2-
from d3fendtools.kuberdf import NS_K8S
2+
from d3fendtools.kuberdf import K8S
33

44

55
class UnionFind:
@@ -52,9 +52,7 @@ def unify_sameas(
5252
index = 0
5353
for root, members in eq_classes.items():
5454
index += 1
55-
union_node = (
56-
NS_K8S["union"] if len(eq_classes) == 1 else NS_K8S[f"union{index}"]
57-
)
55+
union_node = K8S["union"] if len(eq_classes) == 1 else K8S[f"union{index}"]
5856
union_nodes[root] = (union_node, members)
5957

6058
for s, p, o in g:
@@ -77,7 +75,7 @@ def unify_sameas(
7775
# and replicate type relationships on the union node
7876
for root, (union_node, members) in union_nodes.items():
7977
for m in members:
80-
new_g.add((union_node, NS_K8S.hasChild, m))
78+
new_g.add((union_node, K8S.hasChild, m))
8179
# Copy the type triple: if m a T, also union a T
8280
for _, p, t in g.triples((m, RDF.type, None)):
8381
new_g.add((union_node, RDF.type, t))

0 commit comments

Comments
 (0)