Merge pull request #15 from compose-viz/dev

feat: implement parser and graph generator
This commit is contained in:
Xyphuz 2022-05-26 01:38:04 +08:00 committed by GitHub
commit 8068121a4f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
104 changed files with 1606 additions and 1975 deletions

View file

@ -15,3 +15,4 @@ jobs:
uses: JRubics/poetry-publish@v1.10 uses: JRubics/poetry-publish@v1.10
with: with:
pypi_token: ${{ secrets.PYPI_TOKEN }} pypi_token: ${{ secrets.PYPI_TOKEN }}
extra_build_dependency_packages: "graphviz"

View file

@ -16,77 +16,27 @@ jobs:
- name: Switch to Current Branch - name: Switch to Current Branch
run: git checkout ${{ env.BRANCH }} run: git checkout ${{ env.BRANCH }}
- run: |
sudo apt-get update
sudo apt-get install -y graphviz
- name: Validate Test Files
run: |
docker compose -f tests/ymls/builds/docker-compose.yml config -q
docker compose -f tests/ymls/depends_on/docker-compose.yml config -q
docker compose -f tests/ymls/extends/docker-compose.yml config -q
docker compose -f tests/ymls/links/docker-compose.yml config -q
docker compose -f tests/ymls/networks/docker-compose.yml config -q
docker compose -f tests/ymls/ports/docker-compose.yml config -q
docker compose -f tests/ymls/volumes/docker-compose.yml config -q
docker compose -f examples/full-stack-node-app/docker-compose.yml config -q
docker compose -f examples/non-normative/docker-compose.yml config -q
- name: Setup Python 3.10.4 - name: Setup Python 3.10.4
uses: actions/setup-python@v3 uses: actions/setup-python@v3
with: with:
python-version: '3.10.4' python-version: '3.10.4'
- name: Validate Test Files
run: |
docker-compose -f tests/in/000001.yaml config -q
docker-compose -f tests/in/000010.yaml config -q
docker-compose -f tests/in/000011.yaml config -q
docker-compose -f tests/in/000100.yaml config -q
docker-compose -f tests/in/000101.yaml config -q
docker-compose -f tests/in/000110.yaml config -q
docker-compose -f tests/in/000111.yaml config -q
docker-compose -f tests/in/001000.yaml config -q
docker-compose -f tests/in/001001.yaml config -q
docker-compose -f tests/in/001010.yaml config -q
docker-compose -f tests/in/001011.yaml config -q
docker-compose -f tests/in/001100.yaml config -q
docker-compose -f tests/in/001101.yaml config -q
docker-compose -f tests/in/001110.yaml config -q
docker-compose -f tests/in/001111.yaml config -q
docker-compose -f tests/in/010000.yaml config -q
docker-compose -f tests/in/010001.yaml config -q
docker-compose -f tests/in/010010.yaml config -q
docker-compose -f tests/in/010011.yaml config -q
docker-compose -f tests/in/010100.yaml config -q
docker-compose -f tests/in/010101.yaml config -q
docker-compose -f tests/in/010110.yaml config -q
docker-compose -f tests/in/010111.yaml config -q
docker-compose -f tests/in/011000.yaml config -q
docker-compose -f tests/in/011001.yaml config -q
docker-compose -f tests/in/011010.yaml config -q
docker-compose -f tests/in/011011.yaml config -q
docker-compose -f tests/in/011100.yaml config -q
docker-compose -f tests/in/011101.yaml config -q
docker-compose -f tests/in/011110.yaml config -q
docker-compose -f tests/in/011111.yaml config -q
docker-compose -f tests/in/100000.yaml config -q
docker-compose -f tests/in/100001.yaml config -q
docker-compose -f tests/in/100010.yaml config -q
docker-compose -f tests/in/100011.yaml config -q
docker-compose -f tests/in/100100.yaml config -q
docker-compose -f tests/in/100101.yaml config -q
docker-compose -f tests/in/100110.yaml config -q
docker-compose -f tests/in/100111.yaml config -q
docker-compose -f tests/in/101000.yaml config -q
docker-compose -f tests/in/101001.yaml config -q
docker-compose -f tests/in/101010.yaml config -q
docker-compose -f tests/in/101011.yaml config -q
docker-compose -f tests/in/101100.yaml config -q
docker-compose -f tests/in/101101.yaml config -q
docker-compose -f tests/in/101110.yaml config -q
docker-compose -f tests/in/101111.yaml config -q
docker-compose -f tests/in/110000.yaml config -q
docker-compose -f tests/in/110001.yaml config -q
docker-compose -f tests/in/110010.yaml config -q
docker-compose -f tests/in/110011.yaml config -q
docker-compose -f tests/in/110100.yaml config -q
docker-compose -f tests/in/110101.yaml config -q
docker-compose -f tests/in/110110.yaml config -q
docker-compose -f tests/in/110111.yaml config -q
docker-compose -f tests/in/111000.yaml config -q
docker-compose -f tests/in/111001.yaml config -q
docker-compose -f tests/in/111010.yaml config -q
docker-compose -f tests/in/111011.yaml config -q
docker-compose -f tests/in/111100.yaml config -q
docker-compose -f tests/in/111101.yaml config -q
docker-compose -f tests/in/111110.yaml config -q
docker-compose -f tests/in/111111.yaml config -q
- name: Setup Poetry - name: Setup Poetry
uses: Gr1N/setup-poetry@v7 uses: Gr1N/setup-poetry@v7
with: with:
@ -96,6 +46,10 @@ jobs:
run: | run: |
poetry install --no-root poetry install --no-root
- name: Validate Custom Input File - name: Execute pre-commit
run: | run: |
poetry run python -m pytest poetry run python -m pre_commit run --all-files --show-diff-on-failure
- name: Run Pytest
run: |
poetry run python -m pytest --cov=compose_viz tests/ --tb=short

View file

@ -22,6 +22,10 @@ jobs:
- name: Switch to Current Branch - name: Switch to Current Branch
run: git checkout ${{ env.BRANCH }} run: git checkout ${{ env.BRANCH }}
- run: |
sudo apt-get update
sudo apt-get install -y graphviz
- name: Setup Python 3.10.4 - name: Setup Python 3.10.4
uses: actions/setup-python@v3 uses: actions/setup-python@v3
with: with:

3
.gitignore vendored
View file

@ -159,3 +159,6 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
*.png
!examples/**/*.png

38
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,38 @@
exclude: |
(?x)^(
README.md|
LICENSE|
tests/ymls/others/
)
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.2.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/pycqa/flake8
rev: 4.0.1
hooks:
- id: flake8
args:
- "--max-line-length=120"
- repo: https://github.com/pycqa/isort
rev: 5.10.1
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 22.3.0
hooks:
- id: black
args: # arguments to configure black
- --line-length=120
- repo: local
hooks:
- id: pyright
name: pyright
entry: pyright
language: node
pass_filenames: false
types: [python]
additional_dependencies: ['pyright@1.1.247']

View file

@ -38,6 +38,8 @@
<li> <li>
<a href="#getting-started">Getting Started</a> <a href="#getting-started">Getting Started</a>
<ul> <ul>
<li><a href="#prerequisities">Prerequisities</a></li>
<li><a href="#installation">Installation</a></li>
<li><a href="#usage">Usage</a></li> <li><a href="#usage">Usage</a></li>
<li><a href="#options">Options</a></li> <li><a href="#options">Options</a></li>
<li><a href="#example">Example</a></li> <li><a href="#example">Example</a></li>
@ -54,7 +56,9 @@
## About The Project ## About The Project
`compose-viz` is a [docker-compose](https://github.com/docker/compose)/[podman-compose](https://github.com/containers/podman-compose) graph visualization tool that allows you to gernerate graph in [DOT](https://graphviz.org/doc/info/lang.html) format or `.png`. `compose-viz` is a compose file visualization tool that supports [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/spec.md) and allows you to gernerate graph in [DOT](https://graphviz.org/doc/info/lang.html) format or `.png`.
If you are looking for a compose file vizualization tool, and you are using one of the [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/spec.md) implementations (e.g. [docker-compose](https://github.com/docker/compose)/[podman-compose](https://github.com/containers/podman-compose)), then `compose-viz` is a great choice for you.
<p align="right">(<a href="#top">back to top</a>)</p> <p align="right">(<a href="#top">back to top</a>)</p>
@ -62,20 +66,40 @@
## Getting Started ## Getting Started
### Prerequisities
#### Graphviz
If you want to generate PNG (which is the default option), you need to install [Graphviz](https://graphviz.org/download/).
### Installation
#### Using `pip`
`pip install compose-viz`
#### Using `.whl`
See [releases](https://github.com/compose-viz/compose-viz/releases).
### Usage ### Usage
`python3 compose-viz.py [OPTIONS] [input-file]` `cpv [OPTIONS] INPUT_PATH`
### Options ### Options
| Option | Necessity | Description | Default Value | | Option | Description |
| ----------------------------- | --------- | ----------------- | --------------- | | ------------------------ | ------------------------------------------------------------------------------ |
| `-o --output-file` | Optional | Output file path. | `./compose.png` | | `-o, --output-path` | Output path for the generated visualization file. [default: ./compose-viz.png] |
| `-m --output-format=DOT, PNG` | Optional | Output format. | PNG | | `-m, --format [PNG,DOT]` | Output format for the generated visualization file. [default: PNG] |
| `-v, --version` | Show the version of compose-viz. |
| `--help` | Show help and exit. |
### Example ### Example
`python3 compose-viz.py docker-compose.yaml` `cpv -o .\examples\full-stack-node-app\compose-viz.png .\examples\full-stack-node-app\docker-compose.yml`
[Here](https://github.com/compose-viz/compose-viz/blob/main/examples/full-stack-node-app/compose-viz.png) is the result.
<p align="right">(<a href="#top">back to top</a>)</p> <p align="right">(<a href="#top">back to top</a>)</p>
@ -83,7 +107,7 @@
## Roadmap ## Roadmap
- [ ] Support [podman-compose](https://github.com/containers/podman-compose). - [ ] Support more vizualization components.
See the [open issues](https://github.com/compose-viz/compose-viz/issues) See the [open issues](https://github.com/compose-viz/compose-viz/issues)
for a full list of proposed features (and known issues). for a full list of proposed features (and known issues).

View file

@ -1,5 +1,4 @@
from compose_viz.cli import start_cli from compose_viz.cli import start_cli
if __name__ == "__main__": if __name__ == "__main__":
start_cli() start_cli()

View file

@ -1,7 +1,10 @@
from enum import Enum from enum import Enum
import typer
from typing import Optional from typing import Optional
import typer
from compose_viz import __app_name__, __version__ from compose_viz import __app_name__, __version__
from compose_viz.graph import Graph
from compose_viz.parser import Parser from compose_viz.parser import Parser
@ -27,26 +30,26 @@ def _version_callback(value: bool) -> None:
@app.callback() @app.callback()
def compose_viz( def compose_viz(
input_path: str, input_path: str,
output_path: Optional[str] = typer.Option( output_path: str = typer.Option(
None, "./compose-viz.png",
"--output_path", "--output-path",
"-o", "-o",
help="Output path for the generated visualization.", help="Output path for the generated visualization file.",
), ),
format: VisualizationFormats = typer.Option( format: VisualizationFormats = typer.Option(
"PNG", "PNG",
"--format", "--format",
"-m", "-m",
help="Output format for the generated visualization.", help="Output format for the generated visualization file.",
), ),
_: Optional[bool] = typer.Option( _: Optional[bool] = typer.Option(
None, None,
"--version", "--version",
"-v", "-v",
help="Show the version of compose_viz.", help="Show the version of compose-viz.",
callback=_version_callback, callback=_version_callback,
is_eager=True, is_eager=True,
) ),
) -> None: ) -> None:
parser = Parser() parser = Parser()
compose = parser.parse(input_path) compose = parser.parse(input_path)
@ -54,8 +57,10 @@ def compose_viz(
if compose: if compose:
typer.echo(f"Successfully parsed {input_path}") typer.echo(f"Successfully parsed {input_path}")
Graph(compose, output_path).render(format)
raise typer.Exit() raise typer.Exit()
def start_cli() -> None: def start_cli() -> None:
app(prog_name=__app_name__) app(prog_name="cpv")

View file

@ -1,10 +1,12 @@
from typing import List from typing import List
from compose_viz.service import Service from compose_viz.service import Service
class Compose: class Compose:
def __init__(self, services: List[Service]) -> None: def __init__(self, services: List[Service]) -> None:
self.services = services self._services = services
def extract_networks(self) -> List[str]: @property
raise NotImplementedError def services(self):
return self._services

15
compose_viz/extends.py Normal file
View file

@ -0,0 +1,15 @@
from typing import Optional
class Extends:
def __init__(self, service_name: str, from_file: Optional[str] = None):
self._service_name = service_name
self._from_file = from_file
@property
def service_name(self):
return self._service_name
@property
def from_file(self):
return self._from_file

92
compose_viz/graph.py Normal file
View file

@ -0,0 +1,92 @@
from typing import Optional
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",
"dir": "both",
},
"links": {
"style": "solid",
},
"volumes": {
"style": "dashed",
"dir": "both",
},
"depends_on": {
"style": "dotted",
},
"extends": {
"dir": "both",
"arrowhead": "inv",
"arrowtail": "dot",
},
}
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 validate_name(self, name: str) -> str:
# graphviz does not allow ':' in node name
transTable = name.maketrans({":": ""})
return name.translate(transTable)
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))
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))
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")
for network in service.networks:
self.add_vertex(network, "network", lable=f"net:{network}")
self.add_edge(service.name, network, "links")
for volume in service.volumes:
self.add_vertex(volume.source, "volume")
self.add_edge(service.name, volume.source, "volumes", lable=volume.target)
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:
self.add_edge(link.split(":")[0], service.name, "links", link.split(":")[1])
for depends_on in service.depends_on:
self.add_edge(service.name, depends_on, "depends_on")
self.dot.render(outfile=self.filename, format=format, cleanup=cleanup)

View file

@ -1,4 +1,12 @@
from compose_viz.compose import Compose import re
from typing import Dict, List, Optional
from ruamel.yaml import YAML
from compose_viz.compose import Compose, Service
from compose_viz.extends import Extends
from compose_viz.port import Port, Protocol
from compose_viz.volume import Volume, VolumeType
class Parser: class Parser:
@ -6,5 +14,216 @@ class Parser:
pass pass
def parse(self, file_path: str) -> Compose: def parse(self, file_path: str) -> Compose:
# validate input file using `docker-compose config -q sys.argv[1]` first # load the yaml file
raise NotImplementedError with open(file_path, "r") as f:
try:
yaml = YAML(typ="safe", pure=True)
yaml_data = yaml.load(f)
except Exception as e:
raise RuntimeError(f"Error parsing file '{file_path}': {e}")
# validate the yaml file
if not yaml_data:
raise RuntimeError("Empty yaml file, aborting.")
if not yaml_data.get("services"):
raise RuntimeError("No services found, aborting.")
# parse services data into Service objects
services = self.parse_service_data(yaml_data["services"])
# create Compose object
compose = Compose(services)
return compose
def parse_service_data(self, services_yaml_data: Dict[str, dict]) -> List[Service]:
services: List[Service] = []
for service, service_name in zip(services_yaml_data.values(), services_yaml_data.keys()):
service_image: Optional[str] = None
if service.get("build"):
if type(service["build"]) is str:
service_image = f"build from '{service['build']}'"
elif type(service["build"]) is dict:
if service["build"].get("context") and service["build"].get("dockerfile"):
service_image = (
f"build from '{service['build']['context']}' using '{service['build']['dockerfile']}'"
)
elif service["build"].get("context"):
service_image = f"build from '{service['build']['context']}'"
if service.get("image"):
if service_image:
service_image += ", image: " + service["image"]
else:
service_image = service["image"]
service_networks: List[str] = []
if service.get("networks"):
if type(service["networks"]) is list:
service_networks = service["networks"]
elif type(service["networks"]) is dict:
service_networks = list(service["networks"].keys())
service_extends: Optional[Extends] = None
if service.get("extends"):
assert type(service["extends"]) is dict, "Invalid extends format, aborting."
assert service["extends"]["service"], "Missing extends service, aborting."
extend_service_name = str(service["extends"]["service"])
extend_from_file: Optional[str] = None
if service["extends"].get("file"):
assert service["extends"]["file"], "Missing extends file, aborting."
extend_from_file = str(service["extends"]["file"])
service_extends = Extends(service_name=extend_service_name, from_file=extend_from_file)
service_ports: List[Port] = []
if service.get("ports"):
assert type(service["ports"]) is list
for port_data in service["ports"]:
if type(port_data) is dict:
# define a nested function to limit variable scope
def long_syntax():
assert type(port_data) is dict
assert port_data["target"]
container_port: str = str(port_data["target"])
host_port: str = ""
protocol: Protocol = Protocol.any
if port_data.get("published"):
host_port = str(port_data["published"])
else:
host_port = container_port
if port_data.get("host_ip"):
host_ip = str(port_data["host_ip"])
host_port = f"{host_ip}:{host_port}"
else:
host_port = f"0.0.0.0:{host_port}"
if port_data.get("protocol"):
protocol = Protocol[str(port_data["protocol"])]
assert host_port, "Error parsing port, aborting."
service_ports.append(
Port(
host_port=host_port,
container_port=container_port,
protocol=protocol,
)
)
long_syntax()
elif type(port_data) is str:
# ports that needs to parse using regex:
# - "3000"
# - "3000-3005"
# - "8000:8000"
# - "9090-9091:8080-8081"
# - "49100:22"
# - "127.0.0.1:8001:8001"
# - "127.0.0.1:5000-5010:5000-5010"
# - "6060:6060/udp"
def short_syntax():
assert type(port_data) is str
regex = r"(?P<host_ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:)?((?P<host_port>\d+(\-\d+)?):)?((?P<container_port>\d+(\-\d+)?))?(/(?P<protocol>\w+))?" # noqa: E501
match = re.match(regex, port_data)
if match:
host_ip: Optional[str] = match.group("host_ip")
host_port: Optional[str] = match.group("host_port")
container_port: Optional[str] = match.group("container_port")
protocol: Optional[str] = match.group("protocol")
assert container_port, "Invalid port format, aborting."
if container_port and not host_port:
host_port = container_port
if host_ip:
host_port = f"{host_ip}{host_port}"
else:
host_port = f"0.0.0.0:{host_port}"
assert host_port, "Error while parsing port, aborting."
if protocol:
service_ports.append(
Port(
host_port=host_port,
container_port=container_port,
protocol=Protocol[protocol],
)
)
else:
service_ports.append(
Port(
host_port=host_port,
container_port=container_port,
)
)
short_syntax()
service_depends_on: List[str] = []
if service.get("depends_on"):
if type(service["depends_on"]) is list:
for depends_on in service["depends_on"]:
service_depends_on.append(str(depends_on))
elif type(service["depends_on"]) is dict:
service_depends_on = list(service["depends_on"].keys())
service_volumes: List[Volume] = []
if service.get("volumes"):
assert type(service["volumes"]) is list
for volume_data in service["volumes"]:
if type(volume_data) is dict:
assert volume_data["source"] and volume_data["target"], "Invalid volume input, aborting."
volume_source: str = str(volume_data["source"])
volume_target: str = str(volume_data["target"])
volume_type: VolumeType = VolumeType.volume
if volume_data.get("type"):
volume_type = VolumeType[str(volume_data["type"])]
service_volumes.append(Volume(source=volume_source, target=volume_target, type=volume_type))
elif type(volume_data) is str:
assert ":" in volume_data, "Invalid volume input, aborting."
spilt_data = volume_data.split(":")
if len(spilt_data) == 2:
service_volumes.append(Volume(source=spilt_data[0], target=spilt_data[1]))
elif len(spilt_data) == 3:
service_volumes.append(
Volume(source=spilt_data[0], target=spilt_data[1], access_mode=spilt_data[2])
)
service_links: List[str] = []
if service.get("links"):
service_links = service["links"]
services.append(
Service(
name=service_name,
image=service_image,
networks=service_networks,
extends=service_extends,
ports=service_ports,
depends_on=service_depends_on,
volumes=service_volumes,
links=service_links,
)
)
# Service print debug
# print("--------------------")
# print("Service name: {}".format(service_name))
# print("image: {}".format(service_image))
# print("networks: {}".format(service_networks))
# print("image: {}".format(service_image))
# print("extends: {}".format(service_extends))
# print("ports: {}".format(service_ports))
# print("depends: {}".format(service_depends_on))
return services

26
compose_viz/port.py Normal file
View file

@ -0,0 +1,26 @@
from enum import Enum
class Protocol(str, Enum):
tcp = "tcp"
udp = "udp"
any = "any"
class Port:
def __init__(self, host_port: str, container_port: str, protocol: Protocol = Protocol.any):
self._host_port = host_port
self._container_port = container_port
self._protocol = protocol
@property
def host_port(self):
return self._host_port
@property
def container_port(self):
return self._container_port
@property
def protocol(self):
return self._protocol

View file

@ -1,13 +1,59 @@
from typing import List from typing import List, Optional
from compose_viz.extends import Extends
from compose_viz.port import Port
from compose_viz.volume import Volume
class Service: class Service:
def __init__(self, name: str, image: str, ports: List[str] = [], networks: List[str] = [], volumes: List[str] = [], depends_on: List[str] = [], links: List[str] = [], extends: List[str] = []) -> None: def __init__(
self.name = name self,
self.image = image name: str,
self.ports = ports image: Optional[str] = None,
self.networks = networks ports: List[Port] = [],
self.volumes = volumes networks: List[str] = [],
self.depends_on = depends_on volumes: List[Volume] = [],
self.links = links depends_on: List[str] = [],
self.extends = extends links: List[str] = [],
extends: Optional[Extends] = None,
) -> None:
self._name = name
self._image = image
self._ports = ports
self._networks = networks
self._volumes = volumes
self._depends_on = depends_on
self._links = links
self._extends = extends
@property
def name(self):
return self._name
@property
def image(self):
return self._image
@property
def ports(self):
return self._ports
@property
def networks(self):
return self._networks
@property
def volumes(self):
return self._volumes
@property
def depends_on(self):
return self._depends_on
@property
def links(self):
return self._links
@property
def extends(self):
return self._extends

32
compose_viz/volume.py Normal file
View file

@ -0,0 +1,32 @@
from enum import Enum
class VolumeType(str, Enum):
volume = "volume"
bind = "bind"
tmpfs = "tmpfs"
npipe = "npipe"
class Volume:
def __init__(self, source: str, target: str, type: VolumeType = VolumeType.volume, access_mode: str = "rw"):
self._source = source
self._target = target
self._type = type
self._access_mode = access_mode
@property
def source(self):
return self._source
@property
def target(self):
return self._target
@property
def type(self):
return self._type
@property
def access_mode(self):
return self._access_mode

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View file

@ -0,0 +1,87 @@
version: "3.9"
services:
node:
build:
context: .
dockerfile: Dockerfile.node
api:
image: "awesome/api"
extends:
service: node
build:
args:
PACKAGE_PATH: api
WORKING_DIR: /usr/src/
expose:
- 8000
ports:
- 8000:8000
environment:
- NODE_ENV=development
volumes:
- ./api:/usr/src
depends_on:
- db
- adminer
- redis
networks:
- front-tier
- back-tier
command: ["npm", "start"]
frontend:
extends:
service: node
build:
args:
PACKAGE_PATH: frontend
WORKING_DIR: /usr/src/
expose:
- 3000
ports:
- 3000:3000
volumes:
- ./frontend:/usr/src
depends_on:
- api
networks:
- front-tier
command: ["npm", "start"]
db:
image: "awesome/db"
extends:
service: postgres
file: postgres.yml
restart: always
networks:
- back-tier
volumes:
- "db-data:/data"
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
redis:
image: "awesome/redis"
restart: always
networks:
- back-tier
expose:
- 6379
adminer:
image: "awesome/adminer"
networks:
- back-tier
ports:
- 8080:8080
volumes:
db-data:
networks:
front-tier:
back-tier:

View file

@ -0,0 +1,5 @@
version: "3.9"
services:
postgres:
image: awesome/postgres

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View file

@ -12,7 +12,6 @@ services:
backend: backend:
image: awesome/backend
networks: networks:
back-tier: back-tier:
aliases: aliases:

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View file

@ -0,0 +1,89 @@
# https://github.com/docker/labs/blob/master/beginner/chapters/votingapp.md
version: "3.9"
services:
redis:
image: redis:alpine
ports:
- "6379"
networks:
- frontend
deploy:
replicas: 2
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
db:
image: postgres:9.4
volumes:
- db-data:/var/lib/postgresql/data
networks:
- backend
deploy:
placement:
constraints: [node.role == manager]
vote:
image: dockersamples/examplevotingapp_vote:before
ports:
- 5000:80
networks:
- frontend
depends_on:
- redis
deploy:
replicas: 2
update_config:
parallelism: 2
restart_policy:
condition: on-failure
result:
image: dockersamples/examplevotingapp_result:before
ports:
- 5001:80
networks:
- backend
depends_on:
- db
deploy:
replicas: 1
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
worker:
image: dockersamples/examplevotingapp_worker
networks:
- frontend
- backend
deploy:
mode: replicated
replicas: 1
labels: [APP=VOTING]
restart_policy:
condition: on-failure
delay: 10s
max_attempts: 3
window: 120s
placement:
constraints: [node.role == manager]
visualizer:
image: dockersamples/visualizer
ports:
- "8080:8080"
stop_grace_period: 1m30s
volumes:
- /var/run/docker.sock:/var/run/docker.sock
deploy:
placement:
constraints: [node.role == manager]
networks:
frontend:
backend:
volumes:
db-data:

341
poetry.lock generated
View file

@ -20,6 +20,14 @@ docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
[[package]]
name = "cfgv"
version = "3.3.1"
description = "Validate configuration and produce human readable error messages."
category = "dev"
optional = false
python-versions = ">=3.6.1"
[[package]] [[package]]
name = "click" name = "click"
version = "8.1.3" version = "8.1.3"
@ -39,6 +47,64 @@ category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "coverage"
version = "6.3.3"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
tomli = {version = "*", optional = true, markers = "extra == \"toml\""}
[package.extras]
toml = ["tomli"]
[[package]]
name = "distlib"
version = "0.3.4"
description = "Distribution utilities"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "filelock"
version = "3.7.0"
description = "A platform independent file lock."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"]
[[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 = "identify"
version = "2.5.0"
description = "File identification library for Python"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
license = ["ukkonen"]
[[package]] [[package]]
name = "iniconfig" name = "iniconfig"
version = "1.1.1" version = "1.1.1"
@ -47,6 +113,14 @@ category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
[[package]]
name = "nodeenv"
version = "1.6.0"
description = "Node.js virtual environment builder"
category = "dev"
optional = false
python-versions = "*"
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "21.3" version = "21.3"
@ -58,6 +132,18 @@ python-versions = ">=3.6"
[package.dependencies] [package.dependencies]
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]]
name = "platformdirs"
version = "2.5.2"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
[[package]] [[package]]
name = "pluggy" name = "pluggy"
version = "1.0.0" version = "1.0.0"
@ -70,6 +156,22 @@ python-versions = ">=3.6"
dev = ["pre-commit", "tox"] dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"] testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pre-commit"
version = "2.19.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
cfgv = ">=2.0.0"
identify = ">=1.0.0"
nodeenv = ">=0.11.1"
pyyaml = ">=5.1"
toml = "*"
virtualenv = ">=20.0.8"
[[package]] [[package]]
name = "py" name = "py"
version = "1.11.0" version = "1.11.0"
@ -110,6 +212,68 @@ tomli = ">=1.0.0"
[package.extras] [package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
[[package]]
name = "pytest-cov"
version = "3.0.0"
description = "Pytest plugin for measuring coverage."
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
coverage = {version = ">=5.2.1", extras = ["toml"]}
pytest = ">=4.6"
[package.extras]
testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"]
[[package]]
name = "pyyaml"
version = "6.0"
description = "YAML parser and emitter for Python"
category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "ruamel.yaml"
version = "0.17.21"
description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order"
category = "main"
optional = false
python-versions = ">=3"
[package.dependencies]
"ruamel.yaml.clib" = {version = ">=0.2.6", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""}
[package.extras]
docs = ["ryd"]
jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"]
[[package]]
name = "ruamel.yaml.clib"
version = "0.2.6"
description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml"
category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "toml"
version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
category = "dev"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]] [[package]]
name = "tomli" name = "tomli"
version = "2.0.1" version = "2.0.1"
@ -135,10 +299,28 @@ dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)"]
doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "mdx-include (>=1.4.1,<2.0.0)"] doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "mdx-include (>=1.4.1,<2.0.0)"]
test = ["shellingham (>=1.3.0,<2.0.0)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "coverage (>=5.2,<6.0)", "pytest-xdist (>=1.32.0,<2.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "mypy (==0.910)", "black (>=22.3.0,<23.0.0)", "isort (>=5.0.6,<6.0.0)"] test = ["shellingham (>=1.3.0,<2.0.0)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "coverage (>=5.2,<6.0)", "pytest-xdist (>=1.32.0,<2.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "mypy (==0.910)", "black (>=22.3.0,<23.0.0)", "isort (>=5.0.6,<6.0.0)"]
[[package]]
name = "virtualenv"
version = "20.14.1"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[package.dependencies]
distlib = ">=0.3.1,<1"
filelock = ">=3.2,<4"
platformdirs = ">=2,<3"
six = ">=1.9.0,<2"
[package.extras]
docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"]
testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"]
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.9" python-versions = "^3.9"
content-hash = "a80ea7abd86b8e5579a192dfa02a55d2219a3a1850bad12da89c30aa42e99156" content-hash = "e1b68a4c83f398e841e6f38823ea18f3cb27b0b251689d0398ae39c03ddc4a47"
[metadata.files] [metadata.files]
atomicwrites = [ atomicwrites = [
@ -149,6 +331,10 @@ attrs = [
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
] ]
cfgv = [
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
]
click = [ click = [
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
@ -157,18 +343,89 @@ colorama = [
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
] ]
coverage = [
{file = "coverage-6.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df32ee0f4935a101e4b9a5f07b617d884a531ed5666671ff6ac66d2e8e8246d8"},
{file = "coverage-6.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75b5dbffc334e0beb4f6c503fb95e6d422770fd2d1b40a64898ea26d6c02742d"},
{file = "coverage-6.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:114944e6061b68a801c5da5427b9173a0dd9d32cd5fcc18a13de90352843737d"},
{file = "coverage-6.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab88a01cd180b5640ccc9c47232e31924d5f9967ab7edd7e5c91c68eee47a69"},
{file = "coverage-6.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad8f9068f5972a46d50fe5f32c09d6ee11da69c560fcb1b4c3baea246ca4109b"},
{file = "coverage-6.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4cd696aa712e6cd16898d63cf66139dc70d998f8121ab558f0e1936396dbc579"},
{file = "coverage-6.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c1a9942e282cc9d3ed522cd3e3cab081149b27ea3bda72d6f61f84eaf88c1a63"},
{file = "coverage-6.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c06455121a089252b5943ea682187a4e0a5cf0a3fb980eb8e7ce394b144430a9"},
{file = "coverage-6.3.3-cp310-cp310-win32.whl", hash = "sha256:cb5311d6ccbd22578c80028c5e292a7ab9adb91bd62c1982087fad75abe2e63d"},
{file = "coverage-6.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:6d4a6f30f611e657495cc81a07ff7aa8cd949144e7667c5d3e680d73ba7a70e4"},
{file = "coverage-6.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:79bf405432428e989cad7b8bc60581963238f7645ae8a404f5dce90236cc0293"},
{file = "coverage-6.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:338c417613f15596af9eb7a39353b60abec9d8ce1080aedba5ecee6a5d85f8d3"},
{file = "coverage-6.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db094a6a4ae6329ed322a8973f83630b12715654c197dd392410400a5bfa1a73"},
{file = "coverage-6.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1414e8b124611bf4df8d77215bd32cba6e3425da8ce9c1f1046149615e3a9a31"},
{file = "coverage-6.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:93b16b08f94c92cab88073ffd185070cdcb29f1b98df8b28e6649145b7f2c90d"},
{file = "coverage-6.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fbc86ae8cc129c801e7baaafe3addf3c8d49c9c1597c44bdf2d78139707c3c62"},
{file = "coverage-6.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b5ba058610e8289a07db2a57bce45a1793ec0d3d11db28c047aae2aa1a832572"},
{file = "coverage-6.3.3-cp37-cp37m-win32.whl", hash = "sha256:8329635c0781927a2c6ae068461e19674c564e05b86736ab8eb29c420ee7dc20"},
{file = "coverage-6.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:e5af1feee71099ae2e3b086ec04f57f9950e1be9ecf6c420696fea7977b84738"},
{file = "coverage-6.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e814a4a5a1d95223b08cdb0f4f57029e8eab22ffdbae2f97107aeef28554517e"},
{file = "coverage-6.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:61f4fbf3633cb0713437291b8848634ea97f89c7e849c2be17a665611e433f53"},
{file = "coverage-6.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3401b0d2ed9f726fadbfa35102e00d1b3547b73772a1de5508ef3bdbcb36afe7"},
{file = "coverage-6.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8586b177b4407f988731eb7f41967415b2197f35e2a6ee1a9b9b561f6323c8e9"},
{file = "coverage-6.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:892e7fe32191960da559a14536768a62e83e87bbb867e1b9c643e7e0fbce2579"},
{file = "coverage-6.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:afb03f981fadb5aed1ac6e3dd34f0488e1a0875623d557b6fad09b97a942b38a"},
{file = "coverage-6.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cbe91bc84be4e5ef0b1480d15c7b18e29c73bdfa33e07d3725da7d18e1b0aff2"},
{file = "coverage-6.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:91502bf27cbd5c83c95cfea291ef387469f2387508645602e1ca0fd8a4ba7548"},
{file = "coverage-6.3.3-cp38-cp38-win32.whl", hash = "sha256:c488db059848702aff30aa1d90ef87928d4e72e4f00717343800546fdbff0a94"},
{file = "coverage-6.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6534fcdfb5c503affb6b1130db7b5bfc8a0f77fa34880146f7a5c117987d0"},
{file = "coverage-6.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc692c9ee18f0dd3214843779ba6b275ee4bb9b9a5745ba64265bce911aefd1a"},
{file = "coverage-6.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:462105283de203df8de58a68c1bb4ba2a8a164097c2379f664fa81d6baf94b81"},
{file = "coverage-6.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc972d829ad5ef4d4c5fcabd2bbe2add84ce8236f64ba1c0c72185da3a273130"},
{file = "coverage-6.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:06f54765cdbce99901871d50fe9f41d58213f18e98b170a30ca34f47de7dd5e8"},
{file = "coverage-6.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7835f76a081787f0ca62a53504361b3869840a1620049b56d803a8cb3a9eeea3"},
{file = "coverage-6.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6f5fee77ec3384b934797f1873758f796dfb4f167e1296dc00f8b2e023ce6ee9"},
{file = "coverage-6.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:baa8be8aba3dd1e976e68677be68a960a633a6d44c325757aefaa4d66175050f"},
{file = "coverage-6.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4d06380e777dd6b35ee936f333d55b53dc4a8271036ff884c909cf6e94be8b6c"},
{file = "coverage-6.3.3-cp39-cp39-win32.whl", hash = "sha256:f8cabc5fd0091976ab7b020f5708335033e422de25e20ddf9416bdce2b7e07d8"},
{file = "coverage-6.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c9441d57b0963cf8340268ad62fc83de61f1613034b79c2b1053046af0c5284"},
{file = "coverage-6.3.3-pp36.pp37.pp38-none-any.whl", hash = "sha256:d522f1dc49127eab0bfbba4e90fa068ecff0899bbf61bf4065c790ddd6c177fe"},
{file = "coverage-6.3.3.tar.gz", hash = "sha256:2781c43bffbbec2b8867376d4d61916f5e9c4cc168232528562a61d1b4b01879"},
]
distlib = [
{file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"},
{file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"},
]
filelock = [
{file = "filelock-3.7.0-py3-none-any.whl", hash = "sha256:c7b5fdb219b398a5b28c8e4c1893ef5f98ece6a38c6ab2c22e26ec161556fed6"},
{file = "filelock-3.7.0.tar.gz", hash = "sha256:b795f1b42a61bbf8ec7113c341dad679d772567b936fbd1bf43c9a238e673e20"},
]
graphviz = [
{file = "graphviz-0.20-py3-none-any.whl", hash = "sha256:62c5f48bcc534a45b4588c548ff75e419c1f1f3a33d31a91796ae80a7f581e4a"},
{file = "graphviz-0.20.zip", hash = "sha256:76bdfb73f42e72564ffe9c7299482f9d72f8e6cb8d54bce7b48ab323755e9ba5"},
]
identify = [
{file = "identify-2.5.0-py2.py3-none-any.whl", hash = "sha256:3acfe15a96e4272b4ec5662ee3e231ceba976ef63fd9980ed2ce9cc415df393f"},
{file = "identify-2.5.0.tar.gz", hash = "sha256:c83af514ea50bf2be2c4a3f2fb349442b59dc87284558ae9ff54191bff3541d2"},
]
iniconfig = [ iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
] ]
nodeenv = [
{file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"},
{file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"},
]
packaging = [ packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
] ]
platformdirs = [
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
]
pluggy = [ pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
] ]
pre-commit = [
{file = "pre_commit-2.19.0-py2.py3-none-any.whl", hash = "sha256:10c62741aa5704faea2ad69cb550ca78082efe5697d6f04e5710c3c229afdd10"},
{file = "pre_commit-2.19.0.tar.gz", hash = "sha256:4233a1e38621c87d9dda9808c6606d7e7ba0e087cd56d3fe03202a01d2919615"},
]
py = [ py = [
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
@ -181,6 +438,84 @@ pytest = [
{file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
{file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
] ]
pytest-cov = [
{file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
{file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"},
]
pyyaml = [
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
{file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
{file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
{file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
{file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
{file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
{file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
{file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
{file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
{file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
{file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
{file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
{file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
{file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
{file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
]
"ruamel.yaml" = [
{file = "ruamel.yaml-0.17.21-py3-none-any.whl", hash = "sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7"},
{file = "ruamel.yaml-0.17.21.tar.gz", hash = "sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af"},
]
"ruamel.yaml.clib" = [
{file = "ruamel.yaml.clib-0.2.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6e7be2c5bcb297f5b82fee9c665eb2eb7001d1050deaba8471842979293a80b0"},
{file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:221eca6f35076c6ae472a531afa1c223b9c29377e62936f61bc8e6e8bdc5f9e7"},
{file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win32.whl", hash = "sha256:1070ba9dd7f9370d0513d649420c3b362ac2d687fe78c6e888f5b12bf8bc7bee"},
{file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:77df077d32921ad46f34816a9a16e6356d8100374579bc35e15bab5d4e9377de"},
{file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:cfdb9389d888c5b74af297e51ce357b800dd844898af9d4a547ffc143fa56751"},
{file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7b2927e92feb51d830f531de4ccb11b320255ee95e791022555971c466af4527"},
{file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win32.whl", hash = "sha256:ada3f400d9923a190ea8b59c8f60680c4ef8a4b0dfae134d2f2ff68429adfab5"},
{file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win_amd64.whl", hash = "sha256:de9c6b8a1ba52919ae919f3ae96abb72b994dd0350226e28f3686cb4f142165c"},
{file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d67f273097c368265a7b81e152e07fb90ed395df6e552b9fa858c6d2c9f42502"},
{file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:72a2b8b2ff0a627496aad76f37a652bcef400fd861721744201ef1b45199ab78"},
{file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win32.whl", hash = "sha256:9efef4aab5353387b07f6b22ace0867032b900d8e91674b5d8ea9150db5cae94"},
{file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win_amd64.whl", hash = "sha256:846fc8336443106fe23f9b6d6b8c14a53d38cef9a375149d61f99d78782ea468"},
{file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0847201b767447fc33b9c235780d3aa90357d20dd6108b92be544427bea197dd"},
{file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:78988ed190206672da0f5d50c61afef8f67daa718d614377dcd5e3ed85ab4a99"},
{file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win32.whl", hash = "sha256:a49e0161897901d1ac9c4a79984b8410f450565bbad64dbfcbf76152743a0cdb"},
{file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:bf75d28fa071645c529b5474a550a44686821decebdd00e21127ef1fd566eabe"},
{file = "ruamel.yaml.clib-0.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a32f8d81ea0c6173ab1b3da956869114cae53ba1e9f72374032e33ba3118c233"},
{file = "ruamel.yaml.clib-0.2.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7f7ecb53ae6848f959db6ae93bdff1740e651809780822270eab111500842a84"},
{file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win32.whl", hash = "sha256:89221ec6d6026f8ae859c09b9718799fea22c0e8da8b766b0b2c9a9ba2db326b"},
{file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:31ea73e564a7b5fbbe8188ab8b334393e06d997914a4e184975348f204790277"},
{file = "ruamel.yaml.clib-0.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc6a613d6c74eef5a14a214d433d06291526145431c3b964f5e16529b1842bed"},
{file = "ruamel.yaml.clib-0.2.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:1866cf2c284a03b9524a5cc00daca56d80057c5ce3cdc86a52020f4c720856f0"},
{file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win32.whl", hash = "sha256:3fb9575a5acd13031c57a62cc7823e5d2ff8bc3835ba4d94b921b4e6ee664104"},
{file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:825d5fccef6da42f3c8eccd4281af399f21c02b32d98e113dbc631ea6a6ecbc7"},
{file = "ruamel.yaml.clib-0.2.6.tar.gz", hash = "sha256:4ff604ce439abb20794f05613c374759ce10e3595d1867764dd1ae675b85acbd"},
]
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
tomli = [ tomli = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
@ -189,3 +524,7 @@ typer = [
{file = "typer-0.4.1-py3-none-any.whl", hash = "sha256:e8467f0ebac0c81366c2168d6ad9f888efdfb6d4e1d3d5b4a004f46fa444b5c3"}, {file = "typer-0.4.1-py3-none-any.whl", hash = "sha256:e8467f0ebac0c81366c2168d6ad9f888efdfb6d4e1d3d5b4a004f46fa444b5c3"},
{file = "typer-0.4.1.tar.gz", hash = "sha256:5646aef0d936b2c761a10393f0384ee6b5c7fe0bb3e5cd710b17134ca1d99cff"}, {file = "typer-0.4.1.tar.gz", hash = "sha256:5646aef0d936b2c761a10393f0384ee6b5c7fe0bb3e5cd710b17134ca1d99cff"},
] ]
virtualenv = [
{file = "virtualenv-20.14.1-py2.py3-none-any.whl", hash = "sha256:e617f16e25b42eb4f6e74096b9c9e37713cf10bf30168fb4a739f3fa8f898a3a"},
{file = "virtualenv-20.14.1.tar.gz", hash = "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5"},
]

View file

@ -1,7 +1,7 @@
[tool.poetry] [tool.poetry]
name = "compose-viz" name = "compose-viz"
version = "0.1.0" version = "0.1.0"
description = "A docker-compose/podman-compose graph visualization tool that allows you to gernerate graph in DOT format or PNG." description = "A compose file visualization tool that supports compose-spec and allows you to gernerate graph in DOT format or PNG."
authors = ["Xyphuz Wu <xyphuzwu@gmail.com>"] authors = ["Xyphuz Wu <xyphuzwu@gmail.com>"]
readme = "README.md" readme = "README.md"
license = "MIT" license = "MIT"
@ -14,9 +14,15 @@ include = [
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.9" python = "^3.9"
typer = "^0.4.1" typer = "^0.4.1"
PyYAML = "^6.0"
graphviz = "^0.20"
"ruamel.yaml" = "^0.17.21"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pytest = "^7.1.2" pytest = "^7.1.2"
pre-commit = "^2.19.0"
coverage = "^6.3.3"
pytest-cov = "^3.0.0"
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]

View file

@ -1,11 +0,0 @@
services:
base:
image: busybox
user: root
common:
image: busybox
extends:
service: base
cli:
extends:
service: common

View file

@ -1,30 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
extends:
service: frontend
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
extends:
service: frontend
networks:
front-tier:
back-tier:
admin:

View file

@ -1,8 +0,0 @@
version: "3.9"
services:
web:
build: .
ports:
- "8000:5000"
redis:
image: "redis:alpine"

View file

@ -1,32 +0,0 @@
services:
frontend:
image: awesome/webapp
ports:
- "8000:5000"
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
ports:
- "8000:5001"
networks:
- admin
backend:
image: awesome/backend
ports:
- "8000:5010"
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
networks:
front-tier:
back-tier:
admin:

View file

@ -1,17 +0,0 @@
services:
frontend:
image: awesome/webapp
ports:
- "8000:5000"
monitoring:
image: awesome/monitoring
extends:
service: frontend
backend:
image: awesome/backend
extends:
service: frontend
ports:
- "8000:5001"

View file

@ -1,34 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
ports:
- "8000:5000"
monitoring:
image: awesome/monitoring
networks:
- admin
extends:
service: frontend
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
extends:
service: frontend
ports:
- "8000:5001"
networks:
front-tier:
back-tier:
admin:

View file

@ -1,10 +0,0 @@
services:
web:
build: .
depends_on:
- db
- redis
redis:
image: redis
db:
image: postgres

View file

@ -1,29 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
depends_on:
- monitoring
- backend
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
networks:
front-tier:
back-tier:
admin:

View file

@ -1,12 +0,0 @@
services:
web:
build: .
depends_on:
- db
- redis
extends:
service: redis
redis:
image: redis
db:
image: postgres

View file

@ -1,31 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
depends_on:
- monitoring
- backend
extends:
service: backend
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
networks:
front-tier:
back-tier:
admin:

View file

@ -1,17 +0,0 @@
services:
frontend:
image: awesome/webapp
ports:
- "8000:5000"
monitoring:
image: awesome/monitoring
depends_on:
- backend
ports:
- "8000:5010"
backend:
image: awesome/backend
ports:
- "8000:5001"

View file

@ -1,30 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
depends_on:
- backend
ports:
- "8000:5010"
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
networks:
front-tier:
back-tier:
admin:

View file

@ -1,21 +0,0 @@
services:
frontend:
image: awesome/webapp
ports:
- "8000:5000"
monitoring:
image: awesome/monitoring
depends_on:
- backend
extends:
service: frontend
ports:
- "8000:5010"
backend:
image: awesome/backend
extends:
service: frontend
ports:
- "8000:5001"

View file

@ -1,32 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
depends_on:
- backend
extends:
service: frontend
ports:
- "8000:5010"
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
networks:
front-tier:
back-tier:
admin:

View file

@ -1,15 +0,0 @@
services:
backend:
image: awesome/backend
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
volumes:
db-data:

View file

@ -1,37 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
networks:
front-tier:
back-tier:
admin:
volumes:
db-data:

View file

@ -1,13 +0,0 @@
services:
common:
image: busybox
volumes:
- common-volume:/var/lib/backup/data:rw
cli:
extends:
service: common
volumes:
- cli-volume:/var/lib/backup/data:ro
volumes:
common-volume:
cli-volume:

View file

@ -1,39 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
extends:
service: monitoring
networks:
front-tier:
back-tier:
admin:
volumes:
db-data:

View file

@ -1,16 +0,0 @@
services:
backend:
image: awesome/backend
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
ports:
- "8000:5000"
volumes:
db-data:

View file

@ -1,39 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
ports:
- "8000:5000"
networks:
front-tier:
back-tier:
admin:
volumes:
db-data:

View file

@ -1,24 +0,0 @@
services:
frontend:
image: awesome/webapp
monitoring:
image: awesome/monitoring
backend:
image: awesome/backend
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
extends:
service: monitoring
ports:
- "8000:5000"
volumes:
db-data:

View file

@ -1,41 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
extends:
service: monitoring
ports:
- "8000:5000"
networks:
front-tier:
back-tier:
admin:
volumes:
db-data:

View file

@ -1,22 +0,0 @@
services:
frontend:
image: awesome/webapp
monitoring:
image: awesome/monitoring
depends_on:
- backend
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
backend:
image: awesome/backend
volumes:
db-data:

View file

@ -1,40 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
depends_on:
- monitoring
networks:
front-tier:
back-tier:
admin:
volumes:
db-data:

View file

@ -1,26 +0,0 @@
services:
frontend:
image: awesome/webapp
monitoring:
image: awesome/monitoring
backend:
image: awesome/backend
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
depends_on:
- monitoring
extends:
service: frontend
volumes:
db-data:

View file

@ -1,42 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
depends_on:
- monitoring
extends:
service: frontend
networks:
front-tier:
back-tier:
admin:
volumes:
db-data:

View file

@ -1,28 +0,0 @@
services:
frontend:
image: awesome/webapp
ports:
- "8000:5000"
monitoring:
image: awesome/monitoring
depends_on:
- backend
ports:
- "8000:5010"
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
backend:
image: awesome/backend
ports:
- "8000:5001"
volumes:
db-data:

View file

@ -1,74 +0,0 @@
services:
vote:
build: ./
# use python rather than gunicorn for local dev
command: python app.py
depends_on:
redis:
condition: service_healthy
volumes:
- app
ports:
- "5000:80"
networks:
- front-tier
- back-tier
result:
build: ./
# use nodemon rather than node for local dev
command: nodemon server.js
depends_on:
db:
condition: service_healthy
volumes:
- app
ports:
- "5001:80"
- "5858:5858"
networks:
- front-tier
- back-tier
worker:
build:
context: ./
depends_on:
redis:
condition: service_healthy
db:
condition: service_healthy
networks:
- back-tier
redis:
image: redis:5.0-alpine3.10
volumes:
- "./healthchecks:/healthchecks"
healthcheck:
test: /healthchecks/redis.sh
interval: "5s"
ports: ["6379"]
networks:
- back-tier
db:
image: postgres:9.4
environment:
POSTGRES_USER: "postgres"
POSTGRES_PASSWORD: "postgres"
volumes:
- "db-data:/var/lib/postgresql/data"
- "./healthchecks:/healthchecks"
healthcheck:
test: /healthchecks/postgres.sh
interval: "5s"
networks:
- back-tier
volumes:
db-data:
networks:
front-tier:
back-tier:

View file

@ -1,30 +0,0 @@
services:
frontend:
image: awesome/webapp
monitoring:
image: awesome/monitoring
backend:
image: awesome/backend
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
depends_on:
- monitoring
extends:
service: frontend
ports:
- "8000:5010"
volumes:
db-data:

View file

@ -1,44 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
depends_on:
- monitoring
extends:
service: frontend
ports:
- "8000:5010"
networks:
front-tier:
back-tier:
admin:
volumes:
db-data:

View file

@ -1,31 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
links:
- "db:database"
db:
image: postgres
networks:
front-tier:
back-tier:
admin:

View file

@ -1,16 +0,0 @@
services:
frontend:
image: awesome/webapp
monitoring:
image: awesome/monitoring
backend:
image: awesome/backend
extends:
service: frontend
links:
- "db:database"
db:
image: postgres

View file

@ -1,33 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
extends:
service: frontend
links:
- "db:database"
db:
image: postgres
networks:
front-tier:
back-tier:
admin:

View file

@ -1,16 +0,0 @@
services:
frontend:
image: awesome/webapp
monitoring:
image: awesome/monitoring
backend:
image: awesome/backend
ports:
- "8000:5010"
links:
- "db:database"
db:
image: postgres

View file

@ -1,33 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
ports:
- "8000:5010"
links:
- "db:database"
db:
image: postgres
networks:
front-tier:
back-tier:
admin:

View file

@ -1,18 +0,0 @@
services:
frontend:
image: awesome/webapp
monitoring:
image: awesome/monitoring
backend:
image: awesome/backend
extends:
service: frontend
ports:
- "8000:5010"
links:
- "db:database"
db:
image: postgres

View file

@ -1,35 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
extends:
service: frontend
ports:
- "8000:5010"
links:
- "db:database"
db:
image: postgres
networks:
front-tier:
back-tier:
admin:

View file

@ -1,16 +0,0 @@
services:
frontend:
image: awesome/webapp
monitoring:
image: awesome/monitoring
backend:
image: awesome/backend
depends_on:
- monitoring
links:
- "db:database"
db:
image: postgres

View file

@ -1,33 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
depends_on:
- monitoring
links:
- "db:database"
db:
image: postgres
networks:
front-tier:
back-tier:
admin:

View file

@ -1,18 +0,0 @@
services:
frontend:
image: awesome/webapp
monitoring:
image: awesome/monitoring
backend:
image: awesome/backend
depends_on:
- monitoring
links:
- "db:database"
extends:
service: frontend
db:
image: postgres

View file

@ -1,35 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
depends_on:
- monitoring
extends:
service: frontend
links:
- "db:database"
db:
image: postgres
networks:
front-tier:
back-tier:
admin:

View file

@ -1,18 +0,0 @@
services:
frontend:
image: awesome/webapp
monitoring:
image: awesome/monitoring
backend:
image: awesome/backend
depends_on:
- monitoring
ports:
- "8000:5010"
links:
- "db:database"
db:
image: postgres

View file

@ -1,35 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
depends_on:
- monitoring
ports:
- "8000:5010"
links:
- "db:database"
db:
image: postgres
networks:
front-tier:
back-tier:
admin:

View file

@ -1,21 +0,0 @@
services:
frontend:
image: awesome/webapp
monitoring:
image: awesome/monitoring
backend:
image: awesome/backend
depends_on:
- monitoring
extends:
service: frontend
ports:
- "8000:5010"
links:
- "db:database"
db:
image: postgres

View file

@ -1,37 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
depends_on:
- monitoring
extends:
service: frontend
ports:
- "8000:5010"
links:
- "db:database"
db:
image: postgres
networks:
front-tier:
back-tier:
admin:

View file

@ -1,24 +0,0 @@
services:
frontend:
image: awesome/webapp
monitoring:
image: awesome/monitoring
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
links:
- "db:database"
backend:
image: awesome/backend
db:
image: postgres
volumes:
db-data:

View file

@ -1,42 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
links:
- "db:database"
db:
image: postgres
networks:
front-tier:
back-tier:
admin:
volumes:
db-data:

View file

@ -1,28 +0,0 @@
services:
frontend:
image: awesome/webapp
monitoring:
image: awesome/monitoring
backend:
image: awesome/backend
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
extends:
service: frontend
links:
- "db:database"
db:
image: postgres
volumes:
db-data:

View file

@ -1,44 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
extends:
service: frontend
links:
- "db:database"
db:
image: postgres
networks:
front-tier:
back-tier:
admin:
volumes:
db-data:

View file

@ -1,28 +0,0 @@
services:
frontend:
image: awesome/webapp
monitoring:
image: awesome/monitoring
backend:
image: awesome/backend
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
ports:
- "8000:5010"
links:
- "db:database"
db:
image: postgres
volumes:
db-data:

View file

@ -1,44 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
ports:
- "8000:5010"
links:
- "db:database"
db:
image: postgres
networks:
front-tier:
back-tier:
admin:
volumes:
db-data:

View file

@ -1,30 +0,0 @@
services:
frontend:
image: awesome/webapp
monitoring:
image: awesome/monitoring
backend:
image: awesome/backend
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
extends:
service: frontend
ports:
- "8000:5010"
links:
- "db:database"
db:
image: postgres
volumes:
db-data:

View file

@ -1,46 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
extends:
service: frontend
ports:
- "8000:5010"
links:
- "db:database"
db:
image: postgres
networks:
front-tier:
back-tier:
admin:
volumes:
db-data:

View file

@ -1,26 +0,0 @@
services:
frontend:
image: awesome/webapp
monitoring:
image: awesome/monitoring
depends_on:
- backend
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
links:
- "db:database"
backend:
image: awesome/backend
db:
image: postgres
volumes:
db-data:

View file

@ -1,44 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
depends_on:
- monitoring
links:
- "db:database"
db:
image: postgres
networks:
front-tier:
back-tier:
admin:
volumes:
db-data:

View file

@ -1,30 +0,0 @@
services:
frontend:
image: awesome/webapp
monitoring:
image: awesome/monitoring
backend:
image: awesome/backend
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
depends_on:
- monitoring
extends:
service: frontend
links:
- "db:database"
db:
image: postgres
volumes:
db-data:

View file

@ -1,46 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
depends_on:
- monitoring
extends:
service: frontend
links:
- "db:database"
db:
image: postgres
networks:
front-tier:
back-tier:
admin:
volumes:
db-data:

View file

@ -1,30 +0,0 @@
services:
frontend:
image: awesome/webapp
monitoring:
image: awesome/monitoring
backend:
image: awesome/backend
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
depends_on:
- monitoring
ports:
- "8000:5010"
links:
- "db:database"
db:
image: postgres
volumes:
db-data:

View file

@ -1,46 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
depends_on:
- monitoring
ports:
- "8000:5010"
links:
- "db:database"
db:
image: postgres
networks:
front-tier:
back-tier:
admin:
volumes:
db-data:

View file

@ -1,31 +0,0 @@
services:
frontend:
image: awesome/webapp
monitoring:
image: awesome/monitoring
backend:
image: awesome/backend
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
depends_on:
- monitoring
extends:
service: frontend
ports:
- "8000:5010"
links:
- "db:database"
db:
image: postgres
volumes:
db-data:

View file

@ -1,48 +0,0 @@
services:
frontend:
image: awesome/webapp
networks:
- front-tier
- back-tier
monitoring:
image: awesome/monitoring
networks:
- admin
backend:
image: awesome/backend
networks:
back-tier:
aliases:
- database
admin:
aliases:
- mysql
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run/postgres/postgres.sock
depends_on:
- monitoring
extends:
service: frontend
ports:
- "8000:5010"
links:
- "db:database"
db:
image: postgres
networks:
front-tier:
back-tier:
admin:
volumes:
db-data:

View file

@ -1,13 +1,34 @@
from typer.testing import CliRunner import os
from compose_viz import cli
import pytest
from typer.testing import CliRunner
from compose_viz import cli
runner = CliRunner() runner = CliRunner()
def test_cli(): @pytest.mark.parametrize(
input_path = "tests/in/000001.yaml" "test_file_path",
result = runner.invoke(cli.app, [input_path]) [
"tests/ymls/builds/docker-compose.yml",
"tests/ymls/depends_on/docker-compose.yml",
"tests/ymls/extends/docker-compose.yml",
"tests/ymls/links/docker-compose.yml",
"tests/ymls/networks/docker-compose.yml",
"tests/ymls/ports/docker-compose.yml",
"tests/ymls/volumes/docker-compose.yml",
"examples/full-stack-node-app/docker-compose.yml",
"examples/non-normative/docker-compose.yml",
],
)
def test_cli(test_file_path: str) -> None:
input_path = f"{test_file_path}"
output_path = "compose-viz-test.png"
result = runner.invoke(cli.app, ["-o", output_path, input_path])
assert result.exit_code == 0 assert result.exit_code == 0
assert f"Successfully parsed {input_path}\n" in result.stdout assert f"Successfully parsed {input_path}\n" in result.stdout
assert os.path.exists(output_path)
os.remove(output_path)

28
tests/test_extends.py Normal file
View file

@ -0,0 +1,28 @@
import pytest
from compose_viz.extends import Extends
def test_extend_init_normal() -> None:
try:
e = Extends(service_name="frontend", from_file="tests/ymls/others/empty.yaml")
assert e.service_name == "frontend"
assert e.from_file == "tests/ymls/others/empty.yaml"
except Exception as e:
assert False, e
def test_extend_init_without_from_file() -> None:
try:
e = Extends(service_name="frontend")
assert e.service_name == "frontend"
assert e.from_file is None
except Exception as e:
assert False, e
def test_extend_init_without_service_name() -> None:
with pytest.raises(TypeError):
Extends(from_file="tests/ymls/others/empty.yaml") # type: ignore

5
tests/test_module.py Normal file
View file

@ -0,0 +1,5 @@
import os
def test_module():
assert os.system("python -m compose_viz") == 0

View file

@ -1,28 +1,266 @@
from compose_viz.parser import Parser import pytest
from compose_viz.compose import Compose from compose_viz.compose import Compose
from compose_viz.extends import Extends
from compose_viz.parser import Parser
from compose_viz.port import Port, Protocol
from compose_viz.service import Service from compose_viz.service import Service
from compose_viz.volume import Volume, VolumeType
def test_parse_file(): @pytest.mark.parametrize(
expected: Compose = Compose([ "test_file_path, expected",
[
(
"builds/docker-compose",
Compose(
services=[
Service( Service(
name='frontend', name="frontend",
image='awesome/webapp', image="build from './frontend', image: awesome/frontend",
networks=['front-tier', 'back-tier'],
), ),
Service( Service(
name='monitoring', name="backend",
image='awesome/monitoring', image="build from 'backend' using '../backend.Dockerfile'",
networks=['admin'],
), ),
Service( Service(
name='backend', name="db",
image='awesome/backend', image="build from './db'",
networks=['back-tier', 'admin'],
), ),
]) ],
),
),
(
"depends_on/docker-compose",
Compose(
services=[
Service(
name="frontend",
image="awesome/frontend",
depends_on=[
"db",
"redis",
],
),
Service(
name="backend",
image="awesome/backend",
depends_on=[
"db",
"redis",
],
),
Service(
name="db",
image="mysql",
),
Service(
name="redis",
image="redis",
),
],
),
),
(
"extends/docker-compose",
Compose(
services=[
Service(
name="base",
image="alpine:latest",
),
Service(
name="derive_from_base",
image="alpine:edge",
extends=Extends(
service_name="base",
),
),
Service(
name="derive_from_file",
extends=Extends(
service_name="web",
from_file="web.yml",
),
),
],
),
),
(
"links/docker-compose",
Compose(
services=[
Service(
name="frontend",
image="awesome/frontend",
links=[
"db:database",
],
),
Service(
name="db",
image="mysql",
),
],
),
),
(
"networks/docker-compose",
Compose(
services=[
Service(
name="frontend",
image="awesome/frontend",
networks=[
"front-tier",
"back-tier",
],
),
Service(
name="monitoring",
image="awesome/monitoring",
networks=[
"admin",
],
),
Service(
name="backend",
image="awesome/backend",
networks=[
"back-tier",
"admin",
],
),
],
),
),
(
"ports/docker-compose",
Compose(
services=[
Service(
name="frontend",
image="awesome/frontend",
ports=[
Port(
host_port="0.0.0.0:3000",
container_port="3000",
),
Port(
host_port="0.0.0.0:3000-3005",
container_port="3000-3005",
),
Port(
host_port="0.0.0.0:9090-9091",
container_port="8080-8081",
),
Port(
host_port="0.0.0.0:49100",
container_port="22",
),
Port(
host_port="127.0.0.1:8001",
container_port="8001",
),
Port(
host_port="127.0.0.1:5000-5010",
container_port="5000-5010",
),
Port(
host_port="0.0.0.0:6060",
container_port="6060",
protocol=Protocol.udp,
),
Port(
host_port="127.0.0.1:8080",
container_port="80",
protocol=Protocol.tcp,
),
Port(
host_port="0.0.0.0:443",
container_port="443",
),
],
),
],
),
),
(
"volumes/docker-compose",
Compose(
services=[
Service(
name="backend",
image="awesome/backend",
volumes=[
Volume(
source="./data",
target="/data",
),
Volume(
source="/var/run/postgres/postgres.sock",
target="/var/run/postgres/postgres.sock",
type=VolumeType.bind,
),
],
),
Service(
name="common",
image="busybox",
volumes=[
Volume(
source="common-volume",
target="/var/lib/backup/data",
),
],
),
Service(
name="cli",
extends=Extends(
service_name="common",
),
volumes=[
Volume(
source="cli-volume",
target="/var/lib/backup/data",
access_mode="ro,z",
),
],
),
],
),
),
],
)
def test_parse_file(test_file_path: str, expected: Compose) -> None:
parser = Parser() parser = Parser()
actual = parser.parse('tests/in/000001.yaml') actual = parser.parse(f"tests/ymls/{test_file_path}.yml")
assert actual == expected assert len(actual.services) == len(expected.services)
for actual_service, expected_service in zip(actual.services, expected.services):
assert actual_service.name == expected_service.name
assert actual_service.image == expected_service.image
assert len(actual_service.ports) == len(expected_service.ports)
for actual_port, expected_port in zip(actual_service.ports, expected_service.ports):
assert actual_port.host_port == expected_port.host_port
assert actual_port.container_port == expected_port.container_port
assert actual_port.protocol == expected_port.protocol
assert actual_service.networks == expected_service.networks
assert len(actual_service.volumes) == len(expected_service.volumes)
for actual_volume, expected_volume in zip(actual_service.volumes, expected_service.volumes):
assert actual_volume.source == expected_volume.source
assert actual_volume.target == expected_volume.target
assert actual_volume.type == expected_volume.type
assert actual_service.depends_on == expected_service.depends_on
assert actual_service.links == expected_service.links
assert (actual_service.extends is not None) == (expected_service.extends is not None)
if (actual_service.extends is not None) and (expected_service.extends is not None):
assert actual_service.extends.service_name == expected_service.extends.service_name
assert actual_service.extends.from_file == expected_service.extends.from_file

18
tests/test_parser.py Normal file
View file

@ -0,0 +1,18 @@
import pytest
from compose_viz.parser import Parser
def test_parser_error_parsing_file() -> None:
with pytest.raises(RuntimeError, match=r"Error parsing file 'tests/ymls/others/invalid.yml'.*"):
Parser().parse("tests/ymls/others/invalid.yml")
def test_parser_invalid_yaml() -> None:
with pytest.raises(RuntimeError, match=r"Empty yaml file, aborting."):
Parser().parse("tests/ymls/others/empty.yml")
def test_parser_no_services_found() -> None:
with pytest.raises(RuntimeError, match=r"No services found, aborting."):
Parser().parse("tests/ymls/others/no-services.yml")

23
tests/test_port.py Normal file
View file

@ -0,0 +1,23 @@
from compose_viz.port import Port, Protocol
def test_port_init_normal() -> None:
try:
p = Port(host_port="8080", container_port="80")
assert p.host_port == "8080"
assert p.container_port == "80"
assert p.protocol == Protocol.any
except Exception as e:
assert False, e
def test_port_with_protocol() -> None:
try:
p = Port(host_port="8080", container_port="80", protocol=Protocol.udp)
assert p.host_port == "8080"
assert p.container_port == "80"
assert p.protocol == Protocol.udp
except Exception as e:
assert False, e

View file

@ -1,11 +1,11 @@
from typer.testing import CliRunner from typer.testing import CliRunner
from compose_viz import cli, __app_name__, __version__
from compose_viz import __app_name__, __version__, cli
runner = CliRunner() runner = CliRunner()
def test_version(): def test_version() -> None:
result = runner.invoke(cli.app, ["--version"]) result = runner.invoke(cli.app, ["--version"])
assert result.exit_code == 0 assert result.exit_code == 0

37
tests/test_volume.py Normal file
View file

@ -0,0 +1,37 @@
from compose_viz.volume import Volume, VolumeType
def test_volume_init_normal() -> None:
try:
v = Volume(source="./foo", target="./bar")
assert v.source == "./foo"
assert v.target == "./bar"
assert v.type == VolumeType.volume
assert v.access_mode == "rw"
except Exception as e:
assert False, e
def test_volume_with_type() -> None:
try:
v = Volume(source="./foo", target="./bar", type=VolumeType.bind)
assert v.source == "./foo"
assert v.target == "./bar"
assert v.type == VolumeType.bind
assert v.access_mode == "rw"
except Exception as e:
assert False, e
def test_volume_with_access_mode() -> None:
try:
v = Volume(source="./foo", target="./bar", access_mode="ro,z")
assert v.source == "./foo"
assert v.target == "./bar"
assert v.type == VolumeType.volume
assert v.access_mode == "ro,z"
except Exception as e:
assert False, e

View file

@ -0,0 +1,15 @@
version: "3.9"
services:
frontend:
image: awesome/frontend
build: ./frontend
backend:
build:
context: backend
dockerfile: ../backend.Dockerfile
db:
build:
context: ./db

View file

@ -0,0 +1,19 @@
version: "3.9"
services:
frontend:
image: awesome/frontend
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
backend:
image: awesome/backend
depends_on:
- db
- redis
db:
image: mysql
redis:
image: redis

View file

@ -0,0 +1,14 @@
version: "3.9"
services:
base:
image: alpine:latest
tty: true
derive_from_base:
image: alpine:edge
extends:
service: base
derive_from_file:
extends:
file: web.yml
service: web

View file

@ -0,0 +1,5 @@
version: "3.9"
services:
web:
image: awesome/web

View file

@ -1,9 +1,9 @@
version: "3.9" version: "3.9"
services:
web: services:
build: . frontend:
image: awesome/frontend
links: links:
- "db:database" - "db:database"
db: db:
image: postgres image: mysql

View file

@ -1,6 +1,8 @@
version: "3.9"
services: services:
frontend: frontend:
image: awesome/webapp image: awesome/frontend
networks: networks:
- front-tier - front-tier
- back-tier - back-tier

View file

Some files were not shown because too many files have changed in this diff Show more