...
 
......@@ -18,10 +18,10 @@ You can install this tool from PyPI:
```
alcibiade@mobydick:~$ sudo pip3 install docker-ascii-map
Collecting docker-ascii-map
Downloading docker-ascii-map-1.1.0.tar.gz
Downloading docker-ascii-map-1.2.0.tar.gz
Installing collected packages: docker-ascii-map
Running setup.py install for docker-ascii-map ... done
Successfully installed docker-ascii-map-1.1.0
Successfully installed docker-ascii-map-1.2.0
```
......
__version__ = '1.1.0'
__version__ = '1.2.0'
import math
from docker_ascii_map.widget import *
from docker_ascii_map.docker_config import Configuration, Container
from docker_ascii_map.widget import *
def build_container_widget(container: Container, encoding: str) -> Widget:
......@@ -101,7 +100,7 @@ class Renderer:
networks_box = VBox(network_widgets)
bridges_box = VBox(bridge_widgets)
links_box = Links(HBox([bridges_box, networks_box]), links)
links_box = Links(bridges_box, networks_box, links, True)
# Port mapping
......
import docker
from typing import List
import docker
class PortMapping:
def __init__(self, private_port, public_port):
def __init__(self, private_port: int, public_port: int):
self.private_port = private_port
self.public_port = public_port
......@@ -12,19 +12,32 @@ class PortMapping:
return '%d:%d' % (self.public_port, self.private_port)
class VolumeMapping:
def __init__(self, rw: bool, source: str, destination: str):
self.rw = rw
self.source = source
self.destination = destination
def __repr__(self):
return '%s:%s' % (self.source, self.destination)
class Container:
def __init__(self, name: str, status: str, networks: List[str], image: str, ports: List[PortMapping]):
def __init__(self, name: str, status: str, networks: List[str], image: str,
ports: List[PortMapping] = None,
volumes: List[VolumeMapping] = None):
self.name = name
self.status = status
self.networks = networks
self.image = image
self.ports = ports
self.ports = ports if ports is not None else []
self.volumes = volumes if volumes is not None else []
def is_running(self):
return self.status == 'running'
def __repr__(self) -> str:
return '%s - %s - %s %s' % (self.name, self.status, self.networks, self.ports)
return '%s - %s - %s %s %s' % (self.name, self.status, self.networks, self.ports, self.volumes)
class Configuration:
......@@ -52,7 +65,10 @@ class ConfigParser:
image = cinfo['Image']
networks = [n for n in cinfo['NetworkSettings']['Networks'].keys()]
networks.sort()
ports = [PortMapping(p['PrivatePort'], p['PublicPort']) for p in cinfo['Ports'] if 'PublicPort' in p.keys()]
containers.append(Container(name, status, networks, image, ports))
ports = [PortMapping(p['PrivatePort'], p['PublicPort'])
for p in cinfo['Ports'] if 'PublicPort' in p.keys()]
volumes = [VolumeMapping(rw=v['RW'], source=v['Source'], destination=v['Destination'])
for v in cinfo['Mounts']]
containers.append(Container(name, status, networks, image, ports, volumes))
return Configuration(containers)
......@@ -166,32 +166,55 @@ class Paragraph(Widget):
class Links(Widget):
def __init__(self, root: Widget, links: List[Tuple[Widget, Widget]]):
self._root = root
def __init__(self, left: Widget, right: Widget,
links: List[Tuple[Widget, Widget]],
align: bool = False):
self._left = left
self._right = right
self._links = links
self._align = align
def preferred_size(self) -> Size:
return self._root.preferred_size()
return HBox([self._left, self._right]).preferred_size()
def render(self, hints: Hints = None):
raster = self._root.render(hints)
dy_values = []
dy_average = 0
root = HBox([self._left, self._right])
raster = root.render(hints)
for w_src, w_dst in self._links:
bounds_src = raster.origin_bounds(w_src)
bounds_dst = raster.origin_bounds(w_dst)
src_x = bounds_src.x + bounds_src.w
src_y = int(bounds_src.y + bounds_src.h / 2) - 1
dst_x = bounds_dst.x
if src_y in range(bounds_dst.y, bounds_dst.y + bounds_dst.h):
dst_y = src_y
dst_x, dst_y, src_x, src_y = self._compute_link_coords(raster, w_dst, w_src)
raster.draw_line(src_x, src_y, dst_x, dst_y)
dy_values.append(dst_y - src_y)
if self._align and len(dy_values) > 0:
dy_average = int(sum(dy_values) / len(dy_values))
if dy_average != 0:
if dy_average < 0:
root = HBox([self._left, Padding(self._right, Size(0, -dy_average))])
else:
dst_y = int(bounds_dst.y + bounds_dst.h / 2) - 1
root = HBox([Padding(self._left, Size(0, dy_average)), self._right])
raster.draw_line(src_x, src_y, dst_x, dst_y)
raster = root.render(hints)
for w_src, w_dst in self._links:
dst_x, dst_y, src_x, src_y = self._compute_link_coords(raster, w_dst, w_src)
raster.draw_line(src_x, src_y, dst_x, dst_y)
dy_values.append(dst_y - src_y)
return raster
def _compute_link_coords(self, raster, w_dst, w_src):
bounds_src = raster.origin_bounds(w_src)
bounds_dst = raster.origin_bounds(w_dst)
src_x = bounds_src.x + bounds_src.w
src_y = int(bounds_src.y + bounds_src.h / 2) - 1
dst_x = bounds_dst.x
dst_y = int(bounds_dst.y + bounds_dst.h / 2) - 1
return dst_x, dst_y, src_x, src_y
class Annotations(Widget):
def __init__(self, content: Widget, annotations: List[Tuple[Widget, str, str, List[str]]]):
......
......@@ -12,13 +12,13 @@ setup(
setup_requires=['pytest-runner'],
install_requires=['docker-py >= 1.10.0', 'termcolor >= 1.1.0'],
tests_require=['pytest'],
url='https://github.com/ChessCorp/docker-ascii-map',
url='https://gitlab.com/alcibiade/docker-ascii-map',
license='MIT',
author='Yannick Kirschhoffer',
author_email='alcibiade@alcibiade.org',
description='A set of python scripts displaying the local docker containers structure and status on an ascii map.',
classifiers=[
'Development Status :: 4 - Beta',
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
'Topic :: System :: Systems Administration',
......
import unittest
from docker_ascii_map.docker_config import *
from docker_ascii_map.ascii_render import Renderer
from docker_ascii_map.raster import RasterCell
from docker_ascii_map.docker_config import *
class RenderingTests(unittest.TestCase):
......@@ -15,8 +13,8 @@ class RenderingTests(unittest.TestCase):
def test_simple_config(self):
config = Configuration([
Container('n1', 'running', ['net1'], 'group1/image-long-name', []),
Container('n2', 'stopped', ['net2'], 'group2/image-short', []),
Container('n1', 'running', ['net1'], 'group1/image-long-name'),
Container('n2', 'stopped', ['net2'], 'group2/image-short'),
])
renderer = Renderer()
......@@ -33,8 +31,8 @@ class RenderingTests(unittest.TestCase):
def test_ascii_fallback(self):
config = Configuration([
Container('n1', 'running', ['net1'], 'group1/image-long-name', []),
Container('n2', 'stopped', ['net2'], 'group2/image-short', []),
Container('n1', 'running', ['net1'], 'group1/image-long-name'),
Container('n2', 'stopped', ['net2'], 'group2/image-short'),
])
renderer = Renderer()
......@@ -51,8 +49,8 @@ class RenderingTests(unittest.TestCase):
def test_container_color(self):
config = Configuration([
Container('n1', 'running', ['net1'], 'group1/image-long-name', []),
Container('n2', 'stopped', ['net2'], 'group2/image-short', []),
Container('n1', 'running', ['net1'], 'group1/image-long-name'),
Container('n2', 'stopped', ['net2'], 'group2/image-short'),
])
renderer = Renderer()
......@@ -70,9 +68,9 @@ class RenderingTests(unittest.TestCase):
def test_dual_net_config(self):
self.maxDiff = None
config = Configuration([
Container('n1', 'running', ['net1'], 'im', []),
Container('n2', 'running', ['net2'], 'im', []),
Container('n-front', 'running', ['net1', 'net2'], 'httpd:2.4', []),
Container('n1', 'running', ['net1'], 'im'),
Container('n2', 'running', ['net2'], 'im'),
Container('n-front', 'running', ['net1', 'net2'], 'httpd:2.4'),
])
renderer = Renderer()
......@@ -81,10 +79,10 @@ class RenderingTests(unittest.TestCase):
self.assertEqual(
' +- net1 -+\n'
' | [✓] n1 |\n'
' [✓] n-front ------+-----| im |\n'
' httpd:2.4 | +--------+\n'
' | +- net2 -+\n'
' +-----| [✓] n1 |\n'
' | | im |\n'
' [✓] n-front ------+ +--------+\n'
' httpd:2.4 | +- net2 -+\n'
' +-----| [✓] n2 |\n'
' | im |\n'
' +--------+\n'
......@@ -93,10 +91,10 @@ class RenderingTests(unittest.TestCase):
def test_dual_net_sort(self):
self.maxDiff = None
config = Configuration([
Container('n1', 'running', ['net1'], 'im', []),
Container('n2', 'running', ['net2'], 'im', []),
Container('n3', 'running', ['net3'], 'im', []),
Container('n-front', 'running', ['net1', 'net3'], 'httpd:2.4', []),
Container('n1', 'running', ['net1'], 'im'),
Container('n2', 'running', ['net2'], 'im'),
Container('n3', 'running', ['net3'], 'im'),
Container('n-front', 'running', ['net1', 'net3'], 'httpd:2.4'),
])
renderer = Renderer()
......@@ -105,10 +103,10 @@ class RenderingTests(unittest.TestCase):
self.assertEqual(
' +- net1 -+\n'
' | [✓] n1 |\n'
' [✓] n-front ------+-----| im |\n'
' httpd:2.4 | +--------+\n'
' | +- net3 -+\n'
' +-----| [✓] n1 |\n'
' | | im |\n'
' [✓] n-front ------+ +--------+\n'
' httpd:2.4 | +- net3 -+\n'
' +-----| [✓] n3 |\n'
' | im |\n'
' +--------+\n'
......@@ -121,7 +119,7 @@ class RenderingTests(unittest.TestCase):
def test_port_map(self):
self.maxDiff = None
config = Configuration([
Container('n1', 'running', ['net1'], 'im', []),
Container('n1', 'running', ['net1'], 'im'),
Container('n2', 'running', ['net2'], 'im', [PortMapping(private_port=8080, public_port=80)]),
])
renderer = Renderer()
......@@ -198,5 +196,34 @@ class RenderingTests(unittest.TestCase):
self.assertEquals('white', raster.get(3, 5).color)
def test_basic_volume(self):
config = Configuration([
Container('n1', 'running', ['net1'], 'group1/image-long-name',
[PortMapping(private_port=8080, public_port=80)]),
Container('n2', 'stopped', ['net2'], 'group2/image-short', [PortMapping(private_port=22, public_port=22)]),
])
renderer = Renderer()
raster = renderer.render_to_raster(config)
self.assertEquals(
' +- net1 ---------------------+\n'
'80 ]-+ [✓] n1 |\n'
' | group1/image-long-name |\n'
' +----------------------------+\n'
' +- net2 ---------------------+\n'
'22 ]-+ [❌] n2 |\n'
' | group2/image-short |\n'
' +----------------------------+\n',
raster.text(color=False))
self.assertEquals('green', raster.get(0, 1).color)
self.assertEquals('white', raster.get(3, 1).color)
self.assertEquals('red', raster.get(0, 5).color)
self.assertEquals('white', raster.get(3, 5).color)
if __name__ == '__main__':
unittest.main()
......@@ -15,11 +15,26 @@ class ConfigTests(unittest.TestCase):
configuration_parser = ConfigParser()
self.assertEqual([], configuration_parser.get_configuration().containers)
mock_method.assert_called_once_with(all=True)
def test_single_container(self):
with patch.object(Client, 'containers', return_value=[
{'Names': ['/im1'], 'State': 'running', 'Image': 'ubuntu:latest',
'Mounts': [],
'NetworkSettings': {'Networks': {}}, 'Ports': []}]) as mock_method:
configuration_parser = ConfigParser()
self.assertEqual('[\'im1 - running - [] [] []\']', str(configuration_parser.get_configuration()))
mock_method.assert_called_once_with(all=True)
def test_volume(self):
with patch.object(Client, 'containers', return_value=[
{'Names': ['/im1'], 'State': 'running', 'Image': 'ubuntu:latest',
'Mounts': [{'Source': '/home/yk/Documents/hr/pg-data', 'RW': True, 'Mode': '',
'Type': 'bind', 'Destination': '/var/lib/postgresql/data', 'Propagation': ''}],
'NetworkSettings': {'Networks': {}}, 'Ports': []}]) as mock_method:
configuration_parser = ConfigParser()
self.assertEqual('[\'im1 - running - [] []\']', str(configuration_parser.get_configuration()))
self.assertEqual('[\'im1 - running - [] [] [/home/yk/Documents/hr/pg-data:/var/lib/postgresql/data]\']',
str(configuration_parser.get_configuration()))
mock_method.assert_called_once_with(all=True)
import unittest
from docker_ascii_map.widget import *
from docker_ascii_map.raster import Boundary
from docker_ascii_map.widget import *
class ModelTests(unittest.TestCase):
......@@ -130,10 +129,11 @@ class ModelTests(unittest.TestCase):
self.maxDiff = None
w1 = Paragraph(['Hello', 'World !'])
w2 = Paragraph(['Hello', 'World !'])
model = Links(HBox([
model = Links(
Padding(w1, Size(4, 1)),
Padding(w2, Size(12, 3))
]), [(w1, w2)])
Padding(w2, Size(12, 3)),
[(w1, w2)]
)
self.assertEqual(
' \n'
......@@ -149,10 +149,13 @@ class ModelTests(unittest.TestCase):
def test_Links_Up(self):
w1 = Paragraph(['Hello', 'World !'])
w2 = Paragraph(['Hello', 'World !'])
model = Links(HBox([
model = Links(
Padding(w1, Size(4, 4)),
Padding(w2, Size(12, 1))
]), [(w1, w2)])
Padding(w2, Size(12, 1)),
[(w1, w2)]
)
# print(str(model.render()))
self.assertEqual(
' \n'
......@@ -170,15 +173,18 @@ class ModelTests(unittest.TestCase):
def test_Links_Straight(self):
w1 = Paragraph(['Hello', 'World !'])
w2 = Paragraph(['Hello', 'World !'])
model = Links(HBox([
model = Links(
Padding(w1, Size(4, 2)),
Padding(w2, Size(12, 1))
]), [(w1, w2)])
Padding(w2, Size(12, 1)),
[(w1, w2)]
)
# print(model.render())
self.assertEqual(
' \n'
' Hello \n'
' Hello ----------------World ! \n'
' +-------Hello \n'
' Hello --------+ World ! \n'
' World ! \n'
' \n'
' \n'
......