Added functionality for listing docker containers and images

This commit is contained in:
José Carlos Cuevas 2014-07-10 11:11:28 +02:00
parent 82d970defb
commit a15e7728a4
3 changed files with 256 additions and 45 deletions

View file

@ -2,7 +2,18 @@
<!-- Generated with glade 3.16.1 -->
<interface>
<requires lib="gtk+" version="3.10"/>
<object class="GtkListStore" id="ContainerList"/>
<object class="GtkListStore" id="ContainerList">
<columns>
<!-- column-name Name -->
<column type="gchararray"/>
<!-- column-name Image -->
<column type="gchararray"/>
<!-- column-name ContainerID -->
<column type="gchararray"/>
<!-- column-name Status -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkImage" id="FileStockImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
@ -18,7 +29,20 @@
<property name="can_focus">False</property>
<property name="stock">gtk-jump-to</property>
</object>
<object class="GtkListStore" id="ImagesListStore"/>
<object class="GtkListStore" id="ImagesList">
<columns>
<!-- column-name Repository -->
<column type="gchararray"/>
<!-- column-name Tag -->
<column type="gchararray"/>
<!-- column-name Size -->
<column type="gchararray"/>
<!-- column-name Virtual -->
<column type="gchararray"/>
<!-- column-name Created -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkImage" id="PlayStockImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
@ -111,6 +135,7 @@
<property name="can_focus">True</property>
<child>
<object class="GtkToolButton" id="ToolConnect">
<property name="use_action_appearance">True</property>
<property name="related_action">connect_action</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
@ -127,6 +152,7 @@
</child>
<child>
<object class="GtkToolButton" id="Refresh">
<property name="use_action_appearance">True</property>
<property name="related_action">refresh_action</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
@ -152,6 +178,7 @@
</child>
<child>
<object class="GtkToolButton" id="Preferences">
<property name="use_action_appearance">True</property>
<property name="related_action">preferences_action</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
@ -198,7 +225,6 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action_name">start_action</property>
<property name="image">PlayStockImage</property>
<property name="always_show_image">True</property>
</object>
@ -216,7 +242,6 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action_name">stop_action</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
@ -234,7 +259,6 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action_name">attach_action</property>
<property name="image">GoToStockImage</property>
<property name="always_show_image">True</property>
</object>
@ -252,7 +276,6 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action_name">log_action</property>
<property name="image">FileStockImage</property>
<property name="always_show_image">True</property>
</object>
@ -280,9 +303,62 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">ContainerList</property>
<property name="search_column">0</property>
<property name="show_expanders">False</property>
<property name="enable_grid_lines">both</property>
<child internal-child="selection">
<object class="GtkTreeSelection" id="treeview-selection1"/>
</child>
<child>
<object class="GtkTreeViewColumn" id="ContainerName">
<property name="resizable">True</property>
<property name="title" translatable="yes">Name</property>
<property name="clickable">True</property>
<property name="sort_column_id">0</property>
<child>
<object class="GtkCellRendererText" id="ContainerNameText"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ContainerImage">
<property name="resizable">True</property>
<property name="title" translatable="yes">Image</property>
<child>
<object class="GtkCellRendererText" id="ContainerImageText"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ContainerID">
<property name="resizable">True</property>
<property name="title" translatable="yes">ID</property>
<child>
<object class="GtkCellRendererText" id="ContainerIDText"/>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ContainerStatus">
<property name="resizable">True</property>
<property name="title" translatable="yes">Status</property>
<child>
<object class="GtkCellRendererText" id="ContainerStatusText"/>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
@ -328,7 +404,6 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action_name">build_action</property>
<property name="image">GearStockImage</property>
<property name="yalign">0.61000001430511475</property>
<property name="always_show_image">True</property>
@ -347,7 +422,6 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action_name">remove_image_action</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
@ -365,7 +439,6 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action_name">runimage_action</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
@ -391,10 +464,75 @@
<object class="GtkTreeView" id="ImagesListView">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">ImagesListStore</property>
<property name="model">ImagesList</property>
<property name="search_column">0</property>
<property name="enable_grid_lines">both</property>
<child internal-child="selection">
<object class="GtkTreeSelection" id="treeview-selection2"/>
</child>
<child>
<object class="GtkTreeViewColumn" id="ImagesRepo">
<property name="resizable">True</property>
<property name="title" translatable="yes">Repository</property>
<property name="clickable">True</property>
<child>
<object class="GtkCellRendererText" id="RepositoryText"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ImageTag">
<property name="resizable">True</property>
<property name="title" translatable="yes">Tag</property>
<property name="clickable">True</property>
<child>
<object class="GtkCellRendererText" id="TagText"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ImageSize">
<property name="title" translatable="yes">Size</property>
<property name="clickable">True</property>
<child>
<object class="GtkCellRendererText" id="SizeText"/>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ImageVirtualSize">
<property name="resizable">True</property>
<property name="title" translatable="yes">Virtual Size</property>
<property name="clickable">True</property>
<child>
<object class="GtkCellRendererText" id="VirtualSizeText"/>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ImageCreated">
<property name="title" translatable="yes">Created</property>
<property name="clickable">True</property>
<child>
<object class="GtkCellRendererText" id="CreatedText"/>
<attributes>
<attribute name="text">4</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>

View file

@ -161,8 +161,13 @@ class DockerInterface(object):
docker_url = 'unix://var/run/docker.sock',
connect_timeout = 10):
# Connect to the docker server, if this fails we let it go up
self.client = docker.Client(base_url = docker_url,
timeout = connect_timeout)
try:
self.client = docker.Client(base_url = docker_url,
timeout = connect_timeout)
except Exception as e:
self.client = None
raise e
# Since we're connected, let's get all the containers and images available
self.update_images()
@ -174,39 +179,55 @@ class DockerInterface(object):
it in ourselves for future reference.
"""
self.images = []
img_list = self.client.images()
for img_info in img_list:
for repo_tags in img_info['RepoTags']:
repository_and_tags = repo_tags.split(':')
docker_image = DockerImage(repository = repository_and_tags[0],
tag = repository_and_tags[1],
img_id = img_info['Id'],
created = img_info['Created'],
size = img_info['Size'],
virtual_size = img_info['VirtualSize'])
if self.client is not None:
img_list = self.client.images()
for img_info in img_list:
for repo_tags in img_info['RepoTags']:
repository_and_tags = repo_tags.split(':')
docker_image = DockerImage(repository = repository_and_tags[0],
tag = repository_and_tags[1],
img_id = img_info['Id'],
created = img_info['Created'],
size = img_info['Size'],
virtual_size = img_info['VirtualSize'])
self.images.append(docker_image)
self.images.append(docker_image)
else:
raise DockerNotConnectedException()
def update_containers(self):
"""
Ask our docker server the containers created and their status
"""
self.containers = []
containers = self.client.containers(all = True)
for container in containers:
container_object = DockerContainer(container_id = container['Id'],
image = container['Image'],
command = container['Command'],
created = container['Created'],
names = container['Names'])
if self.client is not None:
containers = self.client.containers(all = True)
for container in containers:
container_object = DockerContainer(container_id = container['Id'],
image = container['Image'],
command = container['Command'],
created = container['Created'],
names = container['Names'])
container_object.status_from_string(container['Status'])
self.containers.append(container_object)
container_object.status_from_string(container['Status'])
self.containers.append(container_object)
else:
raise DockerNotConnectedException()
def build(self, path, tag):
"""
Creates a new docker image from a Dockerfile in the specified path
"""
self.client.build(path = path, tag = tag)
self.update_images()
self.update_containers()
if self.client is not None:
self.client.build(path = path, tag = tag)
self.update_images()
self.update_containers()
else:
raise DockerNotConnectedException()
class DockerNotConnectedException(Exception):
pass

View file

@ -20,7 +20,9 @@
from gi.repository import Gtk, Gio
from pkg_resources import resource_string
from docker_iface import DockerInterface, DockerImage, DockerContainer
from docker_iface import DockerInterface, DockerImage, DockerContainer, DockerNotConnectedException
import time
class MainWindow(Gtk.Application):
@ -47,12 +49,62 @@ class MainWindow(Gtk.Application):
self.add_window(self.window)
self.window.show()
def refresh_views(self):
def refresh_views(self, refresh_iface = True):
"""
Refresh the docker interface object and the reload the ListViews
to get a fresh snapshot of the system
"""
print "Not Implemented: refresh_views"
# If we have to refresh the Docker interface, we do so
if refresh_iface:
self.docker.update_images()
self.docker.update_containers()
# Fetch the lists
container_view = self.builder.get_object('ContainerListView')
images_view = self.builder.get_object('ImagesListView')
container_list = container_view.get_model()
image_list = images_view.get_model()
# Clear them all
container_list.clear()
image_list.clear()
# And now we populate them back
for container in self.docker.containers:
status = container.return_status_string()
container_list.append(row = [container.names[0],
unicode(container.image),
unicode(container.container_id)[:12],
status])
for image in self.docker.images:
size_text = self.size_to_human(image.size)
virtual_size_text = self.size_to_human(image.virtual_size)
human_date = time.ctime(int(image.created))
image_list.append(row = [image.repository,
image.tag,
size_text,
virtual_size_text,
unicode(human_date)])
def size_to_human(self, size_in_bytes):
"""
Transforms the size given in bytes to a human readable form, appending
the units and making it a more manageable size
Modified from this: http://stackoverflow.com/questions/13343700/bytes-to-human-readable-and-back-without-data-loss
"""
final_format = u"%(value).2f %(symbol)s"
symbols = (u'B', u'Kb', u'Mb', u'Gb', u'Tb', u'Pb', u'Eb', u'Zb', u'Yb')
prefix = {}
for i, s in enumerate(symbols[1:]):
prefix[s] = 1 << (i+1)*10
for symbol in reversed(symbols[1:]):
if size_in_bytes >= prefix[symbol]:
value = float(size_in_bytes) / prefix[symbol]
return final_format % locals()
return final_format % dict(symbol = symbols[0], value = size_in_bytes)
# Events and "natural" callbacks
@ -67,14 +119,14 @@ class MainWindow(Gtk.Application):
Connection action has been triggered
"""
# TODO: Get the parameters from configuration
try:
self.docker = DockerInterface()
self.refresh_views()
# try:
self.docker = DockerInterface()
self.refresh_views(refresh_iface = False)
except Exception as e:
# FIXME: Show a nicer message with a MessageBox
print u"Error connecting to Docker Server: " + unicode(e)
self.docker = None
# except Exception as e:
# # FIXME: Show a nicer message with a MessageBox
# print u"Error connecting to Docker Server: " + unicode(e)
# self.docker = None
def on_preferences_action_activate(self, obj, event = None):
"""