From dd10aa4eddf9bbe1c9e48b382e3bfcfb0bc1faf6 Mon Sep 17 00:00:00 2001 From: Thomas E Lackey Date: Wed, 16 Jul 2025 19:55:29 -0500 Subject: [PATCH 1/7] Mermaid graphs --- pyproject.toml | 1 + src/stack/checklist/checklist.py | 4 +- src/stack/deploy/stack.py | 21 +++++++ src/stack/graph/__init__.py | 0 src/stack/graph/graph.py | 95 ++++++++++++++++++++++++++++++++ src/stack/main.py | 4 +- 6 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 src/stack/graph/__init__.py create mode 100644 src/stack/graph/graph.py diff --git a/pyproject.toml b/pyproject.toml index e7027fc..2635095 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ dependencies = [ "requests", "expandvars", "mergedeep", + "mermaid-builder", "termcolor", "flake8", "black" diff --git a/src/stack/checklist/checklist.py b/src/stack/checklist/checklist.py index b3dd1a3..d3ec904 100644 --- a/src/stack/checklist/checklist.py +++ b/src/stack/checklist/checklist.py @@ -42,7 +42,7 @@ from stack.util import get_yaml -def constainer_dispostion(parent_stack, image_registry, git_ssh): +def container_disposition(parent_stack, image_registry, git_ssh): ret = {} required_stacks = parent_stack.get_required_stacks_paths() @@ -149,7 +149,7 @@ def command(ctx, stack, image_registry, git_ssh): """check if stack containers are ready""" stack = resolve_stack(stack) - what_needs_done = constainer_dispostion(stack, image_registry, git_ssh) + what_needs_done = container_disposition(stack, image_registry, git_ssh) padding = 8 max_name_len = 0 diff --git a/src/stack/deploy/stack.py b/src/stack/deploy/stack.py index b082c80..7853026 100644 --- a/src/stack/deploy/stack.py +++ b/src/stack/deploy/stack.py @@ -280,6 +280,20 @@ def get_plugin_code_paths(self) -> List[Path]: return list(result) + def http_prefix_for(self, stack_path_or_name): + if not self.is_super_stack(): + return None + + if not os.path.exists(str(stack_path_or_name)): + stack_path_or_name = resolve_stack(stack_path_or_name).file_path.parent + + stacks = self.get_required_stacks() + for s in stacks: + stack_path = determine_fs_path_for_stack(s[constants.ref_key], s[constants.path_key]) + if stack_path == stack_path_or_name: + return s.get(constants.http_proxy_prefix_key, None) + return None + def __str__(self): return str(self.__dict__) @@ -386,6 +400,13 @@ def locate_single_stack(stack_name, search_path=get_dev_root_path(), fail_on_mul def resolve_stack(stack_name): if not stack_name: error_exit("stack name cannot be empty") + + if isinstance(stack_name, Stack): + return stack_name + + if isinstance(stack_name, Path): + stack_name = str(stack_name) + stack = None if stack_name.startswith("/") or (os.path.exists(stack_name) and os.path.isdir(stack_name)): stack = get_parsed_stack_config(stack_name) diff --git a/src/stack/graph/__init__.py b/src/stack/graph/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/stack/graph/graph.py b/src/stack/graph/graph.py new file mode 100644 index 0000000..b857013 --- /dev/null +++ b/src/stack/graph/graph.py @@ -0,0 +1,95 @@ +# Copyright © 2025 Bozeman Pass, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +import click + +from mermaid_builder.flowchart import Chart, Node, NodeShape, Subgraph, ChartDir, ClassDef + +from stack.config.util import get_config_setting +from stack.deploy.stack import resolve_stack +from stack.log import output_main + + +_theme = { + "super_stack": "stroke-width:1px, stroke-dasharray:none, stroke:#254336, fill:#27654A, color:#FFFFFF", + "stack": "stroke-width:1px, stroke-dasharray:none, stroke:#46EDC8, fill:#DEFFF8, color:#378E7A", + "service": "stroke-width:1px, stroke-dasharray:none, stroke:#374D7C, fill:#E2EBFF, color:#374D7C", + "http_service": "stroke-width:1px, stroke-dasharray:none, stroke:#FBB35A, fill:#FFF9C4, color:#AE4466", + "http_port": "stroke-width:1px, stroke-dasharray:none, stroke:#FFEFDB, fill:#FBB35A, color:#AE4466", +} + + +@click.command() +@click.option("--stack", help="name or path of the stack", required=False) +@click.option( + "--deploy-to", + help="cluster system to deploy to (compose or k8s or k8s-kind)", + default=get_config_setting("deploy-to", "compose"), +) +@click.pass_context +def command(ctx, stack, deploy_to): + """generate a mermaid graph of the stack""" + + parent_stack = resolve_stack(stack) + chart = Chart(direction=ChartDir.RL) + + for cls, style in _theme.items(): + chart.add_class_def(ClassDef(cls, f"{cls} {style}")) + + def add_stack(stack, parent_graph=None, parent_stack=None): + subgraph = Subgraph(stack.name) + subgraph.get_id() # we need this to be set + + if stack.is_super_stack(): + chart.attach_class(subgraph.title, "super_stack") + for child in stack.get_required_stacks_paths(): + child = resolve_stack(child) + add_stack(child, parent_graph=subgraph, parent_stack=stack) + else: + chart.attach_class(subgraph.title, "stack") + + for svc in stack.get_services(): + svc_node = Node(id=f"{stack.name}-{svc}", title=svc, shape=NodeShape.SUBROUTINE, class_name="service") + subgraph.add_node(svc_node) + http_targets = stack.get_http_proxy_targets() + for ht in http_targets: + if ht["service"] == svc: + title = f":{ht['port']}{ht.get('path', '')}" + if "k8s" in deploy_to and parent_stack: + title = ht.get("path", "/") + http_prefix = parent_stack.http_prefix_for(stack.file_path.parent) + if http_prefix and http_prefix != "/": + title = f"{http_prefix}{title}" + + http_node = Node(id=f"{stack.name}-{svc}-http", title=title, shape=NodeShape.ASSYMETRIC, class_name="http_port") + chart.add_node(http_node) + chart.add_link_between(http_node, svc_node) + svc_node.class_name = "http_service" + if parent_graph: + for s in parent_graph.subgraphs: + if s.id != subgraph.id: + chart.add_link_between(s.id, subgraph.id) + chart.add_link_between(subgraph.id, s.id) + parent_graph.add_subgraph(subgraph) + else: + chart.add_subgraph(subgraph) + + add_stack(parent_stack) + + out = str(chart) + for line in out.splitlines(): + if "direction" not in line: + output_main(line) diff --git a/src/stack/main.py b/src/stack/main.py index 4fcd5b1..77280ce 100644 --- a/src/stack/main.py +++ b/src/stack/main.py @@ -1,6 +1,5 @@ # Copyright © 2022, 2023 Vulcanize # Copyright © 2025 Bozeman Pass, Inc. -import os # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -16,6 +15,7 @@ # along with this program. If not, see . import click +import os import sys from stack import opts @@ -30,6 +30,7 @@ from stack.config.util import get_config_setting from stack.deploy import deployment from stack.deploy import deployment_create +from stack.graph import graph from stack.init import init from stack.repos import fetch from stack.util import STACK_USE_BUILTIN_STACK @@ -83,6 +84,7 @@ def cli(ctx, profile, quiet, verbose, log_file, dry_run, debug, stack): cli.add_command(config.command, "config") cli.add_command(deployment_create.create, "deploy") cli.add_command(fetch.command, "fetch") +cli.add_command(graph.command, "graph") cli.add_command(init.command, "init") cli.add_command(list_stack.command, "list") cli.add_command(deployment.command, "manage") From d163e3155f9422a47be4fba3f0c877e4887430f5 Mon Sep 17 00:00:00 2001 From: Thomas E Lackey Date: Wed, 16 Jul 2025 22:54:28 -0500 Subject: [PATCH 2/7] k8s fix --- src/stack/graph/graph.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/stack/graph/graph.py b/src/stack/graph/graph.py index b857013..ecdfc15 100644 --- a/src/stack/graph/graph.py +++ b/src/stack/graph/graph.py @@ -67,12 +67,13 @@ def add_stack(stack, parent_graph=None, parent_stack=None): http_targets = stack.get_http_proxy_targets() for ht in http_targets: if ht["service"] == svc: - title = f":{ht['port']}{ht.get('path', '')}" - if "k8s" in deploy_to and parent_stack: + title = ":" + str(ht['port']) + if "k8s" in deploy_to: title = ht.get("path", "/") - http_prefix = parent_stack.http_prefix_for(stack.file_path.parent) - if http_prefix and http_prefix != "/": - title = f"{http_prefix}{title}" + if parent_stack: + http_prefix = parent_stack.http_prefix_for(stack.file_path.parent) + if http_prefix and http_prefix != "/": + title = f"{http_prefix}{title}" http_node = Node(id=f"{stack.name}-{svc}-http", title=title, shape=NodeShape.ASSYMETRIC, class_name="http_port") chart.add_node(http_node) From c7c1c16de6da9ddd04611a230cf1a3283df13174 Mon Sep 17 00:00:00 2001 From: Thomas E Lackey Date: Wed, 16 Jul 2025 23:34:08 -0500 Subject: [PATCH 3/7] new palette --- src/stack/graph/graph.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/stack/graph/graph.py b/src/stack/graph/graph.py index ecdfc15..1abc8ff 100644 --- a/src/stack/graph/graph.py +++ b/src/stack/graph/graph.py @@ -24,11 +24,11 @@ _theme = { - "super_stack": "stroke-width:1px, stroke-dasharray:none, stroke:#254336, fill:#27654A, color:#FFFFFF", - "stack": "stroke-width:1px, stroke-dasharray:none, stroke:#46EDC8, fill:#DEFFF8, color:#378E7A", - "service": "stroke-width:1px, stroke-dasharray:none, stroke:#374D7C, fill:#E2EBFF, color:#374D7C", - "http_service": "stroke-width:1px, stroke-dasharray:none, stroke:#FBB35A, fill:#FFF9C4, color:#AE4466", - "http_port": "stroke-width:1px, stroke-dasharray:none, stroke:#FFEFDB, fill:#FBB35A, color:#AE4466", + "super_stack": "stroke:#6C63FF,fill:#F3F6FD,color:#292088,stroke-width:2px;", + "stack": "stroke:#00C9A7,fill:#EDFDFB,color:#1A3A38,stroke-width:2px;", + "service": "stroke:#43E97B,fill:#F5FFF7,color:#236247,stroke-width:2px;", + "http_service": "stroke:#FFB236,fill:#FFFAF4,color:#7A5800,stroke-width:2px;", + "http_port": "stroke:#FF6363,fill:#FFF5F5,color:#7C2323,stroke-width:2px;", } @@ -47,7 +47,7 @@ def command(ctx, stack, deploy_to): chart = Chart(direction=ChartDir.RL) for cls, style in _theme.items(): - chart.add_class_def(ClassDef(cls, f"{cls} {style}")) + chart.add_class_def(ClassDef(cls, f"{style}")) def add_stack(stack, parent_graph=None, parent_stack=None): subgraph = Subgraph(stack.name) @@ -67,7 +67,7 @@ def add_stack(stack, parent_graph=None, parent_stack=None): http_targets = stack.get_http_proxy_targets() for ht in http_targets: if ht["service"] == svc: - title = ":" + str(ht['port']) + title = ":" + str(ht["port"]) if "k8s" in deploy_to: title = ht.get("path", "/") if parent_stack: From 8433bd43e47f0e09dfc1f7e991649628ac8007b3 Mon Sep 17 00:00:00 2001 From: Thomas E Lackey Date: Wed, 16 Jul 2025 23:46:29 -0500 Subject: [PATCH 4/7] new palette --- src/stack/graph/graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stack/graph/graph.py b/src/stack/graph/graph.py index 1abc8ff..56b34b6 100644 --- a/src/stack/graph/graph.py +++ b/src/stack/graph/graph.py @@ -24,7 +24,7 @@ _theme = { - "super_stack": "stroke:#6C63FF,fill:#F3F6FD,color:#292088,stroke-width:2px;", + "super_stack": "stroke:#FFF176,fill:#FFFEEF,color:#6B5E13,stroke-width:2px;", "stack": "stroke:#00C9A7,fill:#EDFDFB,color:#1A3A38,stroke-width:2px;", "service": "stroke:#43E97B,fill:#F5FFF7,color:#236247,stroke-width:2px;", "http_service": "stroke:#FFB236,fill:#FFFAF4,color:#7A5800,stroke-width:2px;", From 0b6db0977c597015ebfef91b6ce7b3f60ef997af Mon Sep 17 00:00:00 2001 From: Thomas E Lackey Date: Thu, 17 Jul 2025 11:46:02 -0500 Subject: [PATCH 5/7] new palette --- src/stack/deploy/stack.py | 13 ++++++++ src/stack/graph/graph.py | 62 +++++++++++++++++++++++++++------------ 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/src/stack/deploy/stack.py b/src/stack/deploy/stack.py index 7853026..f0d0e03 100644 --- a/src/stack/deploy/stack.py +++ b/src/stack/deploy/stack.py @@ -180,6 +180,19 @@ def get_ports(self): ports[svc_name] = [str(x) for x in svc[constants.ports_key]] return ports + + def get_volumes(self): + volumes = {} + pods = self.get_pod_list() + for pod in pods: + parsed_pod_file = self.load_pod_file(pod) + if constants.services_key in parsed_pod_file: + for svc_name, svc in parsed_pod_file[constants.services_key].items(): + if constants.volumes_key in svc: + volumes[svc_name] = svc[constants.volumes_key] + return volumes + + def get_http_proxy_targets(self, prefix=None): if prefix == "/": prefix = None diff --git a/src/stack/graph/graph.py b/src/stack/graph/graph.py index 56b34b6..2c16002 100644 --- a/src/stack/graph/graph.py +++ b/src/stack/graph/graph.py @@ -29,6 +29,8 @@ "service": "stroke:#43E97B,fill:#F5FFF7,color:#236247,stroke-width:2px;", "http_service": "stroke:#FFB236,fill:#FFFAF4,color:#7A5800,stroke-width:2px;", "http_port": "stroke:#FF6363,fill:#FFF5F5,color:#7C2323,stroke-width:2px;", + "port": "stroke:#26C6DA,fill:#E6FAFB,color:#074953,stroke-width:2px;font-size:x-small;", + "volume": "stroke:#A259DF,fill:#F4EEFB,color:#320963,stroke-width:2px;font-size:x-small;" } @@ -39,8 +41,11 @@ help="cluster system to deploy to (compose or k8s or k8s-kind)", default=get_config_setting("deploy-to", "compose"), ) +@click.option("--show-ports/--no-show-ports", default=False) +@click.option("--show-http-ports/--no-show-http-ports", default=True) +@click.option("--show-volumes/--no-show-volumes", default=True) @click.pass_context -def command(ctx, stack, deploy_to): +def command(ctx, stack, deploy_to, show_ports, show_http_ports, show_volumes): """generate a mermaid graph of the stack""" parent_stack = resolve_stack(stack) @@ -49,7 +54,7 @@ def command(ctx, stack, deploy_to): for cls, style in _theme.items(): chart.add_class_def(ClassDef(cls, f"{style}")) - def add_stack(stack, parent_graph=None, parent_stack=None): + def add_stack(stack, show_http_ports=True, show_ports=False, show_volumes=False, parent_graph=None, parent_stack=None): subgraph = Subgraph(stack.name) subgraph.get_id() # we need this to be set @@ -57,28 +62,47 @@ def add_stack(stack, parent_graph=None, parent_stack=None): chart.attach_class(subgraph.title, "super_stack") for child in stack.get_required_stacks_paths(): child = resolve_stack(child) - add_stack(child, parent_graph=subgraph, parent_stack=stack) + add_stack(child, show_http_ports, show_ports, show_volumes, parent_graph=subgraph, parent_stack=stack) else: chart.attach_class(subgraph.title, "stack") for svc in stack.get_services(): svc_node = Node(id=f"{stack.name}-{svc}", title=svc, shape=NodeShape.SUBROUTINE, class_name="service") subgraph.add_node(svc_node) - http_targets = stack.get_http_proxy_targets() - for ht in http_targets: - if ht["service"] == svc: - title = ":" + str(ht["port"]) - if "k8s" in deploy_to: - title = ht.get("path", "/") - if parent_stack: - http_prefix = parent_stack.http_prefix_for(stack.file_path.parent) - if http_prefix and http_prefix != "/": - title = f"{http_prefix}{title}" - - http_node = Node(id=f"{stack.name}-{svc}-http", title=title, shape=NodeShape.ASSYMETRIC, class_name="http_port") - chart.add_node(http_node) - chart.add_link_between(http_node, svc_node) - svc_node.class_name = "http_service" + + http_targets = [] + + if show_http_ports: + http_targets = stack.get_http_proxy_targets() + for ht in http_targets: + if ht["service"] == svc: + title = ":" + str(ht["port"]) + if "k8s" in deploy_to: + title = ht.get("path", "/") + if parent_stack: + http_prefix = parent_stack.http_prefix_for(stack.file_path.parent) + if http_prefix and http_prefix != "/": + title = f"{http_prefix}{title}" + + http_node = Node(id=f"{stack.name}-{svc}-http", title=title, shape=NodeShape.ASSYMETRIC, class_name="http_port") + chart.add_node(http_node) + chart.add_link_between(http_node, svc_node) + svc_node.class_name = "http_service" + + if show_ports: + ports = stack.get_ports().get(svc, []) + for port in ports: + port_node = Node(id=f"{stack.name}-{svc}-port-{port}", title=f":{port}", shape=NodeShape.ASSYMETRIC, class_name="port") + chart.add_node(port_node) + chart.add_link_between(port_node, svc_node) + + if show_volumes: + volumes = stack.get_volumes().get(svc, []) + for volume in volumes: + volume_node = Node(id=f"{stack.name}-{svc}-volume-{volume}", title=f"{volume}", shape=NodeShape.RECT_ROUND, class_name="volume") + subgraph.add_node(volume_node) + subgraph.add_link_between(svc_node, volume_node) + if parent_graph: for s in parent_graph.subgraphs: if s.id != subgraph.id: @@ -88,7 +112,7 @@ def add_stack(stack, parent_graph=None, parent_stack=None): else: chart.add_subgraph(subgraph) - add_stack(parent_stack) + add_stack(parent_stack, show_http_ports, show_ports, show_volumes) out = str(chart) for line in out.splitlines(): From 714a694dc42b932fbe71f5c41ceb6b56337167a2 Mon Sep 17 00:00:00 2001 From: Thomas E Lackey Date: Thu, 17 Jul 2025 12:05:11 -0500 Subject: [PATCH 6/7] volumes --- src/stack/deploy/stack.py | 2 -- src/stack/graph/graph.py | 43 +++++++++++++++++++++++---------------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/stack/deploy/stack.py b/src/stack/deploy/stack.py index f0d0e03..6769dbb 100644 --- a/src/stack/deploy/stack.py +++ b/src/stack/deploy/stack.py @@ -180,7 +180,6 @@ def get_ports(self): ports[svc_name] = [str(x) for x in svc[constants.ports_key]] return ports - def get_volumes(self): volumes = {} pods = self.get_pod_list() @@ -192,7 +191,6 @@ def get_volumes(self): volumes[svc_name] = svc[constants.volumes_key] return volumes - def get_http_proxy_targets(self, prefix=None): if prefix == "/": prefix = None diff --git a/src/stack/graph/graph.py b/src/stack/graph/graph.py index 2c16002..c009662 100644 --- a/src/stack/graph/graph.py +++ b/src/stack/graph/graph.py @@ -24,13 +24,13 @@ _theme = { - "super_stack": "stroke:#FFF176,fill:#FFFEEF,color:#6B5E13,stroke-width:2px;", - "stack": "stroke:#00C9A7,fill:#EDFDFB,color:#1A3A38,stroke-width:2px;", + "super_stack": "stroke:#FFF176,fill:#FFFEEF,color:#6B5E13,stroke-width:2px,font-size:small;", + "stack": "stroke:#00C9A7,fill:#EDFDFB,color:#1A3A38,stroke-width:2px,font-size:small;", "service": "stroke:#43E97B,fill:#F5FFF7,color:#236247,stroke-width:2px;", "http_service": "stroke:#FFB236,fill:#FFFAF4,color:#7A5800,stroke-width:2px;", - "http_port": "stroke:#FF6363,fill:#FFF5F5,color:#7C2323,stroke-width:2px;", - "port": "stroke:#26C6DA,fill:#E6FAFB,color:#074953,stroke-width:2px;font-size:x-small;", - "volume": "stroke:#A259DF,fill:#F4EEFB,color:#320963,stroke-width:2px;font-size:x-small;" + "http_target": "stroke:#FF6363,fill:#FFF5F5,color:#7C2323,stroke-width:2px;", + "port": "stroke:#26C6DA,fill:#E6FAFB,color:#074953,stroke-width:2px,font-size:x-small;", + "volume": "stroke:#A259DF,fill:#F4EEFB,color:#320963,stroke-width:2px,font-size:x-small;", } @@ -42,10 +42,10 @@ default=get_config_setting("deploy-to", "compose"), ) @click.option("--show-ports/--no-show-ports", default=False) -@click.option("--show-http-ports/--no-show-http-ports", default=True) +@click.option("--show-http-targets/--no-show-http-targets", default=True) @click.option("--show-volumes/--no-show-volumes", default=True) @click.pass_context -def command(ctx, stack, deploy_to, show_ports, show_http_ports, show_volumes): +def command(ctx, stack, deploy_to, show_ports, show_http_targets, show_volumes): """generate a mermaid graph of the stack""" parent_stack = resolve_stack(stack) @@ -54,7 +54,7 @@ def command(ctx, stack, deploy_to, show_ports, show_http_ports, show_volumes): for cls, style in _theme.items(): chart.add_class_def(ClassDef(cls, f"{style}")) - def add_stack(stack, show_http_ports=True, show_ports=False, show_volumes=False, parent_graph=None, parent_stack=None): + def add_stack(stack, show_http_targets=True, show_ports=False, show_volumes=False, parent_graph=None, parent_stack=None): subgraph = Subgraph(stack.name) subgraph.get_id() # we need this to be set @@ -62,7 +62,7 @@ def add_stack(stack, show_http_ports=True, show_ports=False, show_volumes=False, chart.attach_class(subgraph.title, "super_stack") for child in stack.get_required_stacks_paths(): child = resolve_stack(child) - add_stack(child, show_http_ports, show_ports, show_volumes, parent_graph=subgraph, parent_stack=stack) + add_stack(child, show_http_targets, show_ports, show_volumes, parent_graph=subgraph, parent_stack=stack) else: chart.attach_class(subgraph.title, "stack") @@ -70,21 +70,24 @@ def add_stack(stack, show_http_ports=True, show_ports=False, show_volumes=False, svc_node = Node(id=f"{stack.name}-{svc}", title=svc, shape=NodeShape.SUBROUTINE, class_name="service") subgraph.add_node(svc_node) - http_targets = [] - - if show_http_ports: + shown_http_ports = {} + if show_http_targets: http_targets = stack.get_http_proxy_targets() for ht in http_targets: if ht["service"] == svc: - title = ":" + str(ht["port"]) + title = f":{ht["port"]}" if "k8s" in deploy_to: title = ht.get("path", "/") if parent_stack: http_prefix = parent_stack.http_prefix_for(stack.file_path.parent) if http_prefix and http_prefix != "/": title = f"{http_prefix}{title}" + else: + shown_http_ports[svc] = ht["port"] - http_node = Node(id=f"{stack.name}-{svc}-http", title=title, shape=NodeShape.ASSYMETRIC, class_name="http_port") + http_node = Node( + id=f"{stack.name}-{svc}-http", title=title, shape=NodeShape.ASSYMETRIC, class_name="http_target" + ) chart.add_node(http_node) chart.add_link_between(http_node, svc_node) svc_node.class_name = "http_service" @@ -92,14 +95,20 @@ def add_stack(stack, show_http_ports=True, show_ports=False, show_volumes=False, if show_ports: ports = stack.get_ports().get(svc, []) for port in ports: - port_node = Node(id=f"{stack.name}-{svc}-port-{port}", title=f":{port}", shape=NodeShape.ASSYMETRIC, class_name="port") + if svc in shown_http_ports and port == shown_http_ports[svc]: + continue + port_node = Node( + id=f"{stack.name}-{svc}-port-{port}", title=f":{port}", shape=NodeShape.ASSYMETRIC, class_name="port" + ) chart.add_node(port_node) chart.add_link_between(port_node, svc_node) if show_volumes: volumes = stack.get_volumes().get(svc, []) for volume in volumes: - volume_node = Node(id=f"{stack.name}-{svc}-volume-{volume}", title=f"{volume}", shape=NodeShape.RECT_ROUND, class_name="volume") + volume_node = Node( + id=f"{stack.name}-{svc}-volume-{volume}", title=f"{volume}", shape=NodeShape.RECT_ROUND, class_name="volume" + ) subgraph.add_node(volume_node) subgraph.add_link_between(svc_node, volume_node) @@ -112,7 +121,7 @@ def add_stack(stack, show_http_ports=True, show_ports=False, show_volumes=False, else: chart.add_subgraph(subgraph) - add_stack(parent_stack, show_http_ports, show_ports, show_volumes) + add_stack(parent_stack, show_http_targets, show_ports, show_volumes) out = str(chart) for line in out.splitlines(): From 76b1988ae6e0e550c0c9cbc27332fbb6bf72bed4 Mon Sep 17 00:00:00 2001 From: Thomas E Lackey Date: Thu, 17 Jul 2025 12:13:44 -0500 Subject: [PATCH 7/7] graph -> chart --- src/stack/{graph => chart}/__init__.py | 0 src/stack/{graph/graph.py => chart/chart.py} | 0 src/stack/main.py | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/stack/{graph => chart}/__init__.py (100%) rename src/stack/{graph/graph.py => chart/chart.py} (100%) diff --git a/src/stack/graph/__init__.py b/src/stack/chart/__init__.py similarity index 100% rename from src/stack/graph/__init__.py rename to src/stack/chart/__init__.py diff --git a/src/stack/graph/graph.py b/src/stack/chart/chart.py similarity index 100% rename from src/stack/graph/graph.py rename to src/stack/chart/chart.py diff --git a/src/stack/main.py b/src/stack/main.py index 77280ce..335e641 100644 --- a/src/stack/main.py +++ b/src/stack/main.py @@ -23,6 +23,7 @@ from stack import version from stack.build import build, prepare +from stack.chart import chart from stack.checklist import list_stack, checklist from stack.cli_util import StackCLI, load_subcommands_from_stack from stack.command_types import CommandOptions @@ -30,7 +31,6 @@ from stack.config.util import get_config_setting from stack.deploy import deployment from stack.deploy import deployment_create -from stack.graph import graph from stack.init import init from stack.repos import fetch from stack.util import STACK_USE_BUILTIN_STACK @@ -80,11 +80,11 @@ def cli(ctx, profile, quiet, verbose, log_file, dry_run, debug, stack): cli.add_command(build.command, "build") +cli.add_command(chart.command, "chart") cli.add_command(checklist.command, "checklist") cli.add_command(config.command, "config") cli.add_command(deployment_create.create, "deploy") cli.add_command(fetch.command, "fetch") -cli.add_command(graph.command, "graph") cli.add_command(init.command, "init") cli.add_command(list_stack.command, "list") cli.add_command(deployment.command, "manage")