compose-viz/compose_viz/graph.py

97 lines
3.2 KiB
Python
Raw Permalink Normal View History

2022-05-25 11:16:10 +02:00
from typing import Optional
2022-05-16 18:57:14 +02:00
import graphviz
from compose_viz.compose import Compose
def apply_vertex_style(type) -> dict:
style = {
2022-05-18 17:28:18 +02:00
"service": {
"shape": "component",
2022-05-16 18:57:14 +02:00
},
2022-05-18 17:28:18 +02:00
"volume": {
"shape": "folder",
2022-05-16 18:57:14 +02:00
},
2022-05-18 17:28:18 +02:00
"network": {
"shape": "pentagon",
2022-05-16 18:57:14 +02:00
},
2022-05-18 17:28:18 +02:00
"port": {
"shape": "circle",
2022-05-16 18:57:14 +02:00
},
}
return style[type]
def apply_edge_style(type) -> dict:
style = {
2022-05-18 17:28:18 +02:00
"ports": {
"style": "solid",
"dir": "both",
2022-05-16 18:57:14 +02:00
},
2022-05-18 17:28:18 +02:00
"links": {
"style": "solid",
2022-05-16 18:57:14 +02:00
},
2022-05-18 17:28:18 +02:00
"volumes": {
"style": "dashed",
"dir": "both",
2022-05-18 17:28:18 +02:00
},
"depends_on": {
"style": "dotted",
2022-05-16 18:57:14 +02:00
},
2022-05-25 11:16:10 +02:00
"extends": {
"dir": "both",
"arrowhead": "inv",
"arrowtail": "dot",
2022-05-25 11:16:10 +02:00
},
2022-05-16 18:57:14 +02:00
}
return style[type]
class Graph:
def __init__(self, compose: Compose, filename: str) -> None:
self.dot = graphviz.Digraph()
2022-05-18 17:28:18 +02:00
self.dot.attr("graph", background="#ffffff", pad="0.5", ratio="fill")
2022-05-16 18:57:14 +02:00
self.compose = compose
self.filename = filename
def validate_name(self, name: str) -> str:
# graphviz does not allow ':' in node name
transTable = name.maketrans({":": ""})
return name.translate(transTable)
2022-05-16 18:57:14 +02:00
2022-05-25 11:16:10 +02:00
def add_vertex(self, name: str, type: str, lable: Optional[str] = None) -> None:
self.dot.node(self.validate_name(name), lable, **apply_vertex_style(type))
2022-05-25 11:16:10 +02:00
def add_edge(self, head: str, tail: str, type: str, lable: Optional[str] = None) -> None:
self.dot.edge(self.validate_name(head), self.validate_name(tail), lable, **apply_edge_style(type))
2022-05-16 18:57:14 +02:00
def render(self, format: str, cleanup: bool = True) -> None:
for service in self.compose.services:
if service.image is not None:
self.add_vertex(service.name, "service", lable=f"{service.name}\n({service.image})")
if service.extends is not None:
self.add_vertex(service.name, "service", lable=f"{service.name}\n")
self.add_edge(service.extends.service_name, service.name, "extends")
2022-05-16 18:57:14 +02:00
for network in service.networks:
self.add_vertex(network, "network", lable=f"net:{network}")
self.add_edge(service.name, network, "links")
2022-05-16 18:57:14 +02:00
for volume in service.volumes:
2022-05-21 11:41:26 +02:00
self.add_vertex(volume.source, "volume")
self.add_edge(service.name, volume.source, "volumes", lable=volume.target)
2022-05-16 18:57:14 +02:00
for port in service.ports:
self.add_vertex(port.host_port, "port", lable=port.host_port)
self.add_edge(port.host_port, service.name, "ports", lable=port.container_port)
for link in service.links:
2022-05-26 03:54:28 +02:00
if ":" in link:
service_name, alias = link.split(":", 1)
self.add_edge(service_name, service.name, "links", alias)
else:
self.add_edge(link, service.name, "links")
2022-05-16 18:57:14 +02:00
for depends_on in service.depends_on:
self.add_edge(service.name, depends_on, "depends_on")
2022-05-16 18:57:14 +02:00
2022-05-26 05:06:06 +02:00
self.dot.render(outfile=f"{self.filename}.{format}", format=format, cleanup=cleanup)