From 26f704f6e187e07bc04a9fc3dbb6cc764bce754c Mon Sep 17 00:00:00 2001 From: Chuan Ou Yang Date: Tue, 17 May 2022 00:55:57 +0800 Subject: [PATCH 1/2] chore: add graph dependencies --- poetry.lock | 19 ++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 02f538e..e47e55f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -39,6 +39,19 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "graphviz" +version = "0.20" +description = "Simple Python interface for Graphviz" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +dev = ["tox (>=3)", "flake8", "pep8-naming", "wheel", "twine"] +docs = ["sphinx (>=4)", "sphinx-autodoc-typehints", "sphinx-rtd-theme"] +test = ["pytest (>=7)", "pytest-mock (>=3)", "mock (>=4)", "pytest-cov", "coverage"] + [[package]] name = "iniconfig" version = "1.1.1" @@ -146,7 +159,7 @@ test = ["shellingham (>=1.3.0,<2.0.0)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov ( [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "f9abb7a4321c120cbd771d7269efc378a123f9ac9920a9413f3b61f1d6604663" +content-hash = "f9f303620dc1f23552238ab8cd7c4db52ef8ee897076bb941866826bc7004dfb" [metadata.files] atomicwrites = [ @@ -165,6 +178,10 @@ colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] +graphviz = [ + {file = "graphviz-0.20-py3-none-any.whl", hash = "sha256:62c5f48bcc534a45b4588c548ff75e419c1f1f3a33d31a91796ae80a7f581e4a"}, + {file = "graphviz-0.20.zip", hash = "sha256:76bdfb73f42e72564ffe9c7299482f9d72f8e6cb8d54bce7b48ab323755e9ba5"}, +] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, diff --git a/pyproject.toml b/pyproject.toml index cca4290..0d33426 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ include = [ python = "^3.9" typer = "^0.4.1" PyYAML = "^6.0" +graphviz = "^0.20" [tool.poetry.dev-dependencies] pytest = "^7.1.2" From 23793d89026f0ad60c1b48f50f2a761c8998e036 Mon Sep 17 00:00:00 2001 From: Chuan Ou Yang Date: Tue, 17 May 2022 00:57:14 +0800 Subject: [PATCH 2/2] feat: add compose renderer --- compose_viz/cli.py | 4 ++- compose_viz/graph.py | 72 ++++++++++++++++++++++++++++++++++++++++++++ tests/test_cli.py | 3 ++ 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 compose_viz/graph.py diff --git a/compose_viz/cli.py b/compose_viz/cli.py index 00c1ddd..8e4ef9b 100644 --- a/compose_viz/cli.py +++ b/compose_viz/cli.py @@ -3,7 +3,7 @@ import typer from typing import Optional from compose_viz import __app_name__, __version__ from compose_viz.parser import Parser - +from compose_viz.graph import Graph class VisualizationFormats(str, Enum): png = "PNG" @@ -54,6 +54,8 @@ def compose_viz( if compose: typer.echo(f"Successfully parsed {input_path}") + Graph(compose, output_path).render(format) + raise typer.Exit() diff --git a/compose_viz/graph.py b/compose_viz/graph.py new file mode 100644 index 0000000..9f8a7fa --- /dev/null +++ b/compose_viz/graph.py @@ -0,0 +1,72 @@ +import graphviz + +from compose_viz.compose import Compose + + +def apply_vertex_style(type) -> dict: + style = { + 'service': { + 'shape': 'component', + }, + 'volume': { + 'shape': 'folder', + }, + 'network': { + 'shape': 'pentagon', + }, + 'port': { + 'shape': 'circle', + }, + } + + return style[type] + + +def apply_edge_style(type) -> dict: + style = { + 'ports': { + 'style': 'solid', + }, + 'links': { + 'style': 'solid', + }, + 'volumes': { + 'style': 'dashed', + }, + 'depends_on': { + 'style': 'dotted', + } + } + + return style[type] + + +class Graph: + def __init__(self, compose: Compose, filename: str) -> None: + self.dot = graphviz.Digraph() + self.dot.attr('graph', background='#ffffff', pad='0.5', ratio='fill') + self.compose = compose + self.filename = filename + + def add_vertex(self, name: str, type: str) -> None: + self.dot.node(name, **apply_vertex_style(type)) + + def add_edge(self, head: str, tail: str, type: str) -> None: + self.dot.edge(head, tail, **apply_edge_style(type)) + + def render(self, format: str, cleanup: bool = True) -> None: + for service in self.compose.services: + self.add_vertex(service.name, 'service') + for network in service.networks: + self.add_vertex("net#" + network, 'network') + self.add_edge(service.name, "net#" + network, 'links') + for volume in service.volumes: + self.add_vertex(volume, 'volume') + self.add_edge(service.name, volume, 'links') + for port in service.ports: + self.add_vertex(port, 'port') + self.add_edge(service.name, port, 'ports') + for depends_on in service.depends_on: + self.dot.edge(depends_on, service.name, 'depends_on') + + self.dot.render(outfile=self.filename, format=format, cleanup=cleanup) diff --git a/tests/test_cli.py b/tests/test_cli.py index 3f427b5..bc0841e 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,3 +1,5 @@ +import os + from typer.testing import CliRunner from compose_viz import cli @@ -11,3 +13,4 @@ def test_cli(): assert result.exit_code == 0 assert f"Successfully parsed {input_path}\n" in result.stdout + assert os.path.exists("compose-viz.png")