Merge pull request #402 from dnephin/fig_ports.rebase

Fig port command
This commit is contained in:
Aanand Prasad 2014-09-05 11:09:40 -07:00
commit 8d3c9dccc5
9 changed files with 164 additions and 51 deletions

View File

@ -28,6 +28,10 @@ Force stop service containers.
View output from services.
## port
Print the public port for a port binding
## ps
List containers.

View File

@ -9,6 +9,8 @@ class UserError(Exception):
def __unicode__(self):
return self.msg
__str__ = __unicode__
class DockerNotFoundMac(UserError):
def __init__(self):

View File

@ -84,6 +84,7 @@ class TopLevelCommand(Command):
help Get help on a command
kill Kill containers
logs View output from containers
port Print the public port for a port binding
ps List containers
rm Remove stopped containers
run Run a one-off command
@ -148,6 +149,26 @@ class TopLevelCommand(Command):
print("Attaching to", list_containers(containers))
LogPrinter(containers, attach_params={'logs': True}, monochrome=monochrome).run()
def port(self, project, options):
"""
Print the public port for a port binding.
Usage: port [options] SERVICE PRIVATE_PORT
Options:
--protocol=proto tcp or udp (defaults to tcp)
--index=index index of the container if there are multiple
instances of a service (defaults to 1)
"""
service = project.get_service(options['SERVICE'])
try:
container = service.get_container(number=options.get('--index') or 1)
except ValueError as e:
raise UserError(str(e))
print(container.get_local_port(
options['PRIVATE_PORT'],
protocol=options.get('--protocol') or 'tcp') or '')
def ps(self, project, options):
"""
List containers.

View File

@ -1,6 +1,8 @@
from __future__ import unicode_literals
from __future__ import absolute_import
from fig.packages import six
class Container(object):
"""
@ -63,17 +65,20 @@ class Container(object):
return None
@property
def human_readable_ports(self):
def ports(self):
self.inspect_if_not_inspected()
if not self.dictionary['NetworkSettings']['Ports']:
return ''
ports = []
for private, public in list(self.dictionary['NetworkSettings']['Ports'].items()):
if public:
ports.append('%s->%s' % (public[0]['HostPort'], private))
else:
ports.append(private)
return ', '.join(ports)
return self.dictionary['NetworkSettings']['Ports'] or {}
@property
def human_readable_ports(self):
def format_port(private, public):
if not public:
return private
return '{HostIp}:{HostPort}->{private}'.format(
private=private, **public[0])
return ', '.join(format_port(*item)
for item in sorted(six.iteritems(self.ports)))
@property
def human_readable_state(self):
@ -105,6 +110,10 @@ class Container(object):
self.inspect_if_not_inspected()
return self.dictionary['State']['Running']
def get_local_port(self, port, protocol='tcp'):
port = self.ports.get("%s/%s" % (port, protocol))
return "{HostIp}:{HostPort}".format(**port[0]) if port else None
def start(self, **options):
return self.client.start(self.id, **options)

View File

@ -78,9 +78,22 @@ class Service(object):
name = get_container_name(container)
if not name or not is_valid_name(name, one_off):
return False
project, name, number = parse_name(name)
project, name, _number = parse_name(name)
return project == self.project and name == self.name
def get_container(self, number=1):
"""Return a :class:`fig.container.Container` for this service. The
container must be active, and match `number`.
"""
for container in self.client.containers():
if not self.has_container(container):
continue
_, _, container_number = parse_name(get_container_name(container))
if container_number == number:
return Container.from_ps(self.client, container)
raise ValueError("No container found for %s_%s" % (self.name, number))
def start(self, **options):
for c in self.containers(stopped=True):
self.start_container_if_stopped(c, **options)

7
tests/fixtures/ports-figfile/fig.yml vendored Normal file
View File

@ -0,0 +1,7 @@
simple:
image: busybox:latest
command: /bin/sleep 300
ports:
- '3000'
- '9999:3001'

View File

@ -1,10 +1,12 @@
from __future__ import absolute_import
from .testcases import DockerClientTestCase
from mock import patch
from fig.cli.main import TopLevelCommand
from fig.packages.six import StringIO
import sys
from fig.packages.six import StringIO
from mock import patch
from .testcases import DockerClientTestCase
from fig.cli.main import TopLevelCommand
class CLITestCase(DockerClientTestCase):
def setUp(self):
@ -213,3 +215,17 @@ class CLITestCase(DockerClientTestCase):
self.command.scale(project, {'SERVICE=NUM': ['simple=0', 'another=0']})
self.assertEqual(len(project.get_service('simple').containers()), 0)
self.assertEqual(len(project.get_service('another').containers()), 0)
def test_port(self):
self.command.base_dir = 'tests/fixtures/ports-figfile'
self.command.dispatch(['up', '-d'], None)
container = self.project.get_service('simple').get_container()
@patch('sys.stdout', new_callable=StringIO)
def get_port(number, mock_stdout):
self.command.dispatch(['port', 'simple', str(number)], None)
return mock_stdout.getvalue().rstrip()
self.assertEqual(get_port(3000), container.get_local_port(3000))
self.assertEqual(get_port(3001), "0.0.0.0:9999")
self.assertEqual(get_port(3002), "")

View File

@ -8,18 +8,28 @@ from fig.container import Container
class ContainerTest(unittest.TestCase):
def setUp(self):
self.container_dict = {
"Id": "abc",
"Image": "busybox:latest",
"Command": "sleep 300",
"Created": 1387384730,
"Status": "Up 8 seconds",
"Ports": None,
"SizeRw": 0,
"SizeRootFs": 0,
"Names": ["/figtest_db_1"],
"NetworkSettings": {
"Ports": {},
},
}
def test_from_ps(self):
container = Container.from_ps(None, {
"Id":"abc",
"Image":"busybox:latest",
"Command":"sleep 300",
"Created":1387384730,
"Status":"Up 8 seconds",
"Ports":None,
"SizeRw":0,
"SizeRootFs":0,
"Names":["/figtest_db_1"]
}, has_been_inspected=True)
container = Container.from_ps(None,
self.container_dict,
has_been_inspected=True)
self.assertEqual(container.dictionary, {
"Id": "abc",
"Image":"busybox:latest",
@ -42,35 +52,21 @@ class ContainerTest(unittest.TestCase):
})
def test_number(self):
container = Container.from_ps(None, {
"Id":"abc",
"Image":"busybox:latest",
"Command":"sleep 300",
"Created":1387384730,
"Status":"Up 8 seconds",
"Ports":None,
"SizeRw":0,
"SizeRootFs":0,
"Names":["/figtest_db_1"]
}, has_been_inspected=True)
container = Container.from_ps(None,
self.container_dict,
has_been_inspected=True)
self.assertEqual(container.number, 1)
def test_name(self):
container = Container.from_ps(None, {
"Id":"abc",
"Image":"busybox:latest",
"Command":"sleep 300",
"Names":["/figtest_db_1"]
}, has_been_inspected=True)
container = Container.from_ps(None,
self.container_dict,
has_been_inspected=True)
self.assertEqual(container.name, "figtest_db_1")
def test_name_without_project(self):
container = Container.from_ps(None, {
"Id":"abc",
"Image":"busybox:latest",
"Command":"sleep 300",
"Names":["/figtest_db_1"]
}, has_been_inspected=True)
container = Container.from_ps(None,
self.container_dict,
has_been_inspected=True)
self.assertEqual(container.name_without_project, "db_1")
def test_inspect_if_not_inspected(self):
@ -85,3 +81,27 @@ class ContainerTest(unittest.TestCase):
container.inspect_if_not_inspected()
self.assertEqual(mock_client.inspect_container.call_count, 1)
def test_human_readable_ports_none(self):
container = Container(None, self.container_dict, has_been_inspected=True)
self.assertEqual(container.human_readable_ports, '')
def test_human_readable_ports_public_and_private(self):
self.container_dict['NetworkSettings']['Ports'].update({
"45454/tcp": [ { "HostIp": "0.0.0.0", "HostPort": "49197" } ],
"45453/tcp": [],
})
container = Container(None, self.container_dict, has_been_inspected=True)
expected = "45453/tcp, 0.0.0.0:49197->45454/tcp"
self.assertEqual(container.human_readable_ports, expected)
def test_get_local_port(self):
self.container_dict['NetworkSettings']['Ports'].update({
"45454/tcp": [ { "HostIp": "0.0.0.0", "HostPort": "49197" } ],
})
container = Container(None, self.container_dict, has_been_inspected=True)
self.assertEqual(
container.get_local_port(45454, protocol='tcp'),
'0.0.0.0:49197')

View File

@ -5,6 +5,8 @@ import os
from .. import unittest
import mock
from fig.packages import docker
from fig import Service
from fig.service import (
ConfigError,
@ -97,14 +99,33 @@ class ServiceTest(unittest.TestCase):
def test_split_domainname_weird(self):
service = Service('foo',
hostname = 'name.sub',
domainname = 'domain.tld',
hostname='name.sub',
domainname='domain.tld',
)
service.next_container_name = lambda x: 'foo'
opts = service._get_container_create_options({})
self.assertEqual(opts['hostname'], 'name.sub', 'hostname')
self.assertEqual(opts['domainname'], 'domain.tld', 'domainname')
def test_get_container_not_found(self):
mock_client = mock.create_autospec(docker.Client)
mock_client.containers.return_value = []
service = Service('foo', client=mock_client)
self.assertRaises(ValueError, service.get_container)
@mock.patch('fig.service.Container', autospec=True)
def test_get_container(self, mock_container_class):
mock_client = mock.create_autospec(docker.Client)
container_dict = dict(Name='default_foo_2')
mock_client.containers.return_value = [container_dict]
service = Service('foo', client=mock_client)
container = service.get_container(number=2)
self.assertEqual(container, mock_container_class.from_ps.return_value)
mock_container_class.from_ps.assert_called_once_with(
mock_client, container_dict)
class ServiceVolumesTest(unittest.TestCase):