cloudmap 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,47 @@
1
+ import csv
2
+ import io
3
+
4
+
5
+ def to_csv(graph, seed_id, meta=None):
6
+ """Render the graph as a flat CSV for auditing and spreadsheet analysis."""
7
+ output = io.StringIO()
8
+ writer = csv.writer(output)
9
+
10
+ # Write header
11
+ writer.writerow([
12
+ "Source Name",
13
+ "Source Type",
14
+ "Dependency Kind",
15
+ "Target Name",
16
+ "Target Type",
17
+ "Trust Level",
18
+ "Evidence",
19
+ "Source ID",
20
+ "Target ID"
21
+ ])
22
+
23
+ for edge in graph.edges:
24
+ source_node = graph.nodes[edge.source]
25
+ target_node = graph.nodes.get(edge.target)
26
+
27
+ target_name = target_node.name if target_node else edge.target.split("/")[-1]
28
+ target_type = target_node.type if target_node else "unknown"
29
+ target_external = target_node.external if target_node else True
30
+
31
+ trust_level = "GUESS (LLM)" if edge.origin == "model" else "Verified"
32
+ if target_external:
33
+ trust_level += " (Unverified Target)"
34
+
35
+ writer.writerow([
36
+ source_node.name,
37
+ source_node.type,
38
+ edge.kind,
39
+ target_name,
40
+ target_type,
41
+ trust_level,
42
+ edge.evidence,
43
+ source_node.id,
44
+ edge.target
45
+ ])
46
+
47
+ return output.getvalue()
@@ -0,0 +1,149 @@
1
+ """Render a graph as an editable .drawio file using native Azure2 icons.
2
+
3
+ Icons are referenced as draw.io's built-in Azure2 image shapes
4
+ (`img/lib/azure2/<category>/<Icon>.svg`) - we emit the style string, draw.io
5
+ supplies the SVG, so no icon assets are shipped in this repo. Any resource type
6
+ without a mapped icon falls back to a labelled rounded box (never a broken
7
+ image). Entries marked (best-effort) may need a filename tweak if the icon
8
+ renders broken in your draw.io build.
9
+ """
10
+
11
+ from xml.sax.saxutils import quoteattr
12
+
13
+ # Verified against jgraph/drawio src/main/webapp/img/lib/azure2/ (folder + filename).
14
+ AZURE_ICON = {
15
+ "microsoft.web/sites": "compute/App_Services.svg",
16
+ "microsoft.web/serverfarms": "app_services/App_Service_Plans.svg",
17
+ "microsoft.keyvault/vaults": "security/Key_Vaults.svg",
18
+ "microsoft.containerservice/managedclusters": "compute/Kubernetes_Services.svg",
19
+ "microsoft.storage/storageaccounts": "storage/Storage_Accounts.svg",
20
+ "microsoft.network/virtualnetworks": "networking/Virtual_Networks.svg",
21
+ "microsoft.network/applicationgateways": "networking/Application_Gateways.svg",
22
+ "microsoft.network/privateendpoints": "networking/Private_Endpoint.svg",
23
+ "microsoft.managedidentity/userassignedidentities": "identity/Managed_Identities.svg",
24
+ "microsoft.containerregistry/registries": "containers/Container_Registries.svg",
25
+ "microsoft.operationalinsights/workspaces": "analytics/Log_Analytics_Workspaces.svg",
26
+ "microsoft.sql/servers": "databases/SQL_Server.svg",
27
+ "microsoft.insights/components": "devops/Application_Insights.svg",
28
+ }
29
+
30
+ ICON_STYLE = ("shape=image;html=1;image=img/lib/azure2/{path};"
31
+ "verticalLabelPosition=bottom;verticalAlign=top;aspect=fixed;fontSize=11;")
32
+ BOX_STYLE = "rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=11;"
33
+ EXT_STYLE = ("rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#999999;"
34
+ "dashed=1;fontColor=#666666;fontSize=11;")
35
+ SEED_EXTRA = "strokeColor=#d79b00;strokeWidth=3;"
36
+ EDGE_STYLE = ("edgeStyle=orthogonalEdgeStyle;rounded=1;html=1;endArrow=block;"
37
+ "fontSize=10;fontColor=#555555;")
38
+ # Model-proposed edges are drawn dashed + red so a guess never looks like a fact.
39
+ EDGE_MODEL_STYLE = EDGE_STYLE + "dashed=1;strokeColor=#b85450;fontColor=#b85450;"
40
+ # RBAC edges are drawn orange/thick to highlight security access paths
41
+ EDGE_RBAC_STYLE = EDGE_STYLE + "strokeColor=#d79b00;fontColor=#b07d00;strokeWidth=2;"
42
+
43
+
44
+ def _short_type(t):
45
+ return t.split("/")[-1] if "/" in t else t
46
+
47
+
48
+ def to_drawio(graph, seed_id):
49
+ dist = graph.distances or {n: 0 for n in graph.nodes}
50
+
51
+ layers = {}
52
+ for nid in graph.nodes:
53
+ layers.setdefault(dist.get(nid, 0), []).append(nid)
54
+
55
+ xstep, ystep = 240, 120
56
+ pos = {}
57
+
58
+ # Calculate max height to vertically center smaller layers
59
+ max_rows = max((len(col) for col in layers.values()), default=0)
60
+ max_height = max_rows * ystep
61
+
62
+ for layer in sorted(layers):
63
+ col = sorted(layers[layer], key=lambda i: graph.nodes[i].name)
64
+ layer_height = len(col) * ystep
65
+ y_offset = (max_height - layer_height) // 2
66
+
67
+ for row, nid in enumerate(col):
68
+ pos[nid] = (60 + layer * xstep, 60 + y_offset + row * ystep)
69
+
70
+ cells, idmap = [], {}
71
+ for i, nid in enumerate(graph.nodes):
72
+ cid = f"n{i}"
73
+ idmap[nid] = cid
74
+ n = graph.nodes[nid]
75
+ label = f"{n.name} ({_short_type(n.type)})"
76
+ icon = None if n.external else AZURE_ICON.get(n.type)
77
+ if icon:
78
+ style, w, h = ICON_STYLE.format(path=icon), 48, 48
79
+ elif n.external:
80
+ style, w, h = EXT_STYLE, 180, 50
81
+ else:
82
+ style, w, h = BOX_STYLE, 170, 50
83
+ if nid == seed_id:
84
+ style += SEED_EXTRA
85
+ x, y = pos[nid]
86
+
87
+ attrs = [
88
+ f'id="{cid}"',
89
+ f'label={quoteattr(label)}',
90
+ f'type={quoteattr(n.type)}',
91
+ ]
92
+ if n.resource_group:
93
+ attrs.append(f'ResourceGroup={quoteattr(n.resource_group)}')
94
+ if n.location:
95
+ attrs.append(f'Location={quoteattr(n.location)}')
96
+ if n.subscription:
97
+ attrs.append(f'Subscription={quoteattr(n.subscription)}')
98
+ if n.note:
99
+ attrs.append(f'Note={quoteattr(n.note)}')
100
+ if not str(nid).startswith("type::") and not str(nid).startswith("ext::"):
101
+ attrs.append(f'ARM_ID={quoteattr(str(nid))}')
102
+
103
+ attr_str = " ".join(attrs)
104
+
105
+ cells.append(
106
+ f' <object {attr_str}>\n'
107
+ f' <mxCell style={quoteattr(style)} vertex="1" parent="1">\n'
108
+ f' <mxGeometry x="{x}" y="{y}" width="{w}" height="{h}" as="geometry"/>\n'
109
+ f' </mxCell>\n'
110
+ f' </object>'
111
+ )
112
+
113
+ for j, e in enumerate(graph.edges):
114
+ s, t = idmap.get(e.source), idmap.get(e.target)
115
+ if not (s and t):
116
+ continue
117
+ model = e.origin == "model"
118
+ rbac = "role:" in e.kind.lower()
119
+
120
+ if model:
121
+ style = EDGE_MODEL_STYLE
122
+ elif rbac:
123
+ style = EDGE_RBAC_STYLE
124
+ else:
125
+ style = EDGE_STYLE
126
+
127
+ label = e.kind + (" (model)" if model else "")
128
+ cells.append(
129
+ f' <mxCell id="e{j}" value={quoteattr(label)} '
130
+ f'style={quoteattr(style)} edge="1" parent="1" '
131
+ f'source="{s}" target="{t}"><mxGeometry relative="1" as="geometry"/></mxCell>'
132
+ )
133
+
134
+ body = "\n".join(cells)
135
+ return (
136
+ '<mxfile host="cloudmap">\n'
137
+ ' <diagram id="cloudmap" name="Blast radius">\n'
138
+ ' <mxGraphModel dx="800" dy="600" grid="1" gridSize="10" guides="1" '
139
+ 'tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" '
140
+ 'pageWidth="1600" pageHeight="1200" math="0" shadow="0">\n'
141
+ ' <root>\n'
142
+ ' <mxCell id="0"/>\n'
143
+ ' <mxCell id="1" parent="0"/>\n'
144
+ f'{body}\n'
145
+ ' </root>\n'
146
+ ' </mxGraphModel>\n'
147
+ ' </diagram>\n'
148
+ '</mxfile>\n'
149
+ )