Skip to content

Commit

Permalink
Add a CLI command to view volumes.
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Nephin <dnephin@gmail.com>
  • Loading branch information
dnephin committed Dec 6, 2014
1 parent 65ae22e commit 056dbaa
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 26 deletions.
63 changes: 40 additions & 23 deletions fig/cli/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import print_function
from __future__ import unicode_literals
import itertools
import logging
import sys
import re
Expand All @@ -15,7 +16,7 @@
from .command import Command
from .formatter import Formatter
from .log_printer import LogPrinter
from .utils import yesno
from .utils import trim, yesno

from docker.errors import APIError
from .errors import UserError
Expand Down Expand Up @@ -95,6 +96,7 @@ class TopLevelCommand(Command):
stop Stop services
restart Restart services
up Create and start containers
volumes List volumes used by containers
"""
def docopt_options(self):
Expand Down Expand Up @@ -187,33 +189,23 @@ def ps(self, project, options):
Options:
-q Only display IDs
"""
containers = sorted(
project.containers(service_names=options['SERVICE'], stopped=True) +
project.containers(service_names=options['SERVICE'], one_off=True),
key=attrgetter('name'))
containers = get_sorted_containers(project, options['SERVICE'])

if options['-q']:
for container in containers:
print(container.id)
else:
headers = [
'Name',
'Command',
'State',
'Ports',
return

def build_row(container):
return [
container.name,
trim(container.human_readable_command, 30),
container.human_readable_state,
container.human_readable_ports,
]
rows = []
for container in containers:
command = container.human_readable_command
if len(command) > 30:
command = '%s ...' % command[:26]
rows.append([
container.name,
command,
container.human_readable_state,
container.human_readable_ports,
])
print(Formatter().table(headers, rows))

headers = ['Name', 'Command', 'State', 'Ports']
print(Formatter().table(headers, map(build_row, containers)))

def pull(self, project, options):
"""
Expand Down Expand Up @@ -450,6 +442,31 @@ def handler(signal, frame):
print("Gracefully stopping... (press Ctrl+C again to force)")
project.stop(service_names=service_names)

def volumes(self, project, options):
"""
List volumes used by containers.
Usage: volumes [SERVICE...]
"""
containers = get_sorted_containers(project, options['SERVICE'])

def build_row(container):
return [(container.name,) + volume for volume in container.volumes]

headers = ['Name', 'Path', 'Mode', 'Host']
print(Formatter().table(headers, list(flat_map(build_row, containers))))


def get_sorted_containers(project, services):
return sorted(
project.containers(service_names=services, stopped=True) +
project.containers(service_names=services, one_off=True),
key=attrgetter('name'))


def flat_map(func, seq):
return itertools.chain.from_iterable(map(func, seq))


def list_containers(containers):
return ", ".join(c.name for c in containers)
6 changes: 6 additions & 0 deletions fig/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ def prettydate(d):
return '{0} hours ago'.format(s / 3600)


def trim(source, length, extra=' ...'):
if len(source) > length:
return '%s%s' % (source[:length - len(extra)], extra)
return source


def mkdir(path, permissions=0o700):
if not os.path.exists(path):
os.mkdir(path)
Expand Down
18 changes: 17 additions & 1 deletion fig/container.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from __future__ import unicode_literals
from __future__ import absolute_import
from collections import namedtuple

import six


Volume = namedtuple('Volume', 'path mode host')


class Container(object):
"""
Represents a Docker container, constructed from the output of
Expand Down Expand Up @@ -66,7 +70,6 @@ def number(self):

@property
def ports(self):
self.inspect_if_not_inspected()
return self.get('NetworkSettings.Ports') or {}

@property
Expand Down Expand Up @@ -101,6 +104,19 @@ def environment(self):
def is_running(self):
return self.get('State.Running')

@property
def volumes(self):
def get_mode(is_rw):
if is_rw is None:
return ''
return 'rw' if is_rw else 'ro'

def get_volume(volume_item):
path, host = volume_item
return Volume(path, get_mode(self.get('VolumesRW').get(path)), host)

return map(get_volume, six.iteritems(self.get('Volumes')))

def get(self, key):
"""Return a value from the container or None if the value is not set.
Expand Down
11 changes: 11 additions & 0 deletions tests/fixtures/volumes-figfile/fig.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

one:
image: busybox
volumes:
- '/etc'
- '/home:/home:ro'

two:
image: busybox
volumes_from:
- one
10 changes: 10 additions & 0 deletions tests/integration/cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ def test_ps_alternate_figfile(self, mock_stdout):
self.assertNotIn('multiplefigfiles_another_1', output)
self.assertIn('multiplefigfiles_yetanother_1', output)

@patch('sys.stdout', new_callable=StringIO)
def test_volumes(self, mock_stdout):
self.command.base_dir = 'tests/fixtures/volumes-figfile'
self.command.dispatch(['up', '-d'], None)
self.command.dispatch(['volumes'], None)

output = mock_stdout.getvalue()
self.assertIn('/etc', output)
self.assertIn('/home', output)

@patch('fig.service.log')
def test_pull(self, mock_logging):
self.command.dispatch(['pull'], None)
Expand Down
24 changes: 22 additions & 2 deletions tests/unit/container_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
import mock
import docker

from fig.container import Container
from fig.container import Container, Volume


class ContainerTest(unittest.TestCase):


def setUp(self):
self.container_dict = {
"Id": "abc",
Expand Down Expand Up @@ -106,6 +105,27 @@ def test_get_local_port(self):
container.get_local_port(45454, protocol='tcp'),
'0.0.0.0:49197')

def test_volumes(self):
container = Container(None, {
"Volumes": {
"/sys": "/sys",
"/var/lib/docker": "/var/lib/docker",
"/etc": "/var/lib/docker/vfs/dir/531d0515",
},
"VolumesRW": {
"/sys": False,
"/var/lib/docker": True,
"/etc": True,
}
}, has_been_inspected=True)
self.assertEqual(
sorted(container.volumes),
[
Volume('/etc', 'rw', '/var/lib/docker/vfs/dir/531d0515'),
Volume('/sys', 'ro', '/sys'),
Volume('/var/lib/docker', 'rw', '/var/lib/docker'),
])

def test_get(self):
container = Container(None, {
"Status":"Up 8 seconds",
Expand Down

0 comments on commit 056dbaa

Please sign in to comment.