mirror of https://github.com/docker/compose.git
commit
8d3c9dccc5
|
@ -28,6 +28,10 @@ Force stop service containers.
|
||||||
|
|
||||||
View output from services.
|
View output from services.
|
||||||
|
|
||||||
|
## port
|
||||||
|
|
||||||
|
Print the public port for a port binding
|
||||||
|
|
||||||
## ps
|
## ps
|
||||||
|
|
||||||
List containers.
|
List containers.
|
||||||
|
|
|
@ -9,6 +9,8 @@ class UserError(Exception):
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.msg
|
return self.msg
|
||||||
|
|
||||||
|
__str__ = __unicode__
|
||||||
|
|
||||||
|
|
||||||
class DockerNotFoundMac(UserError):
|
class DockerNotFoundMac(UserError):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
|
@ -84,6 +84,7 @@ class TopLevelCommand(Command):
|
||||||
help Get help on a command
|
help Get help on a command
|
||||||
kill Kill containers
|
kill Kill containers
|
||||||
logs View output from containers
|
logs View output from containers
|
||||||
|
port Print the public port for a port binding
|
||||||
ps List containers
|
ps List containers
|
||||||
rm Remove stopped containers
|
rm Remove stopped containers
|
||||||
run Run a one-off command
|
run Run a one-off command
|
||||||
|
@ -148,6 +149,26 @@ class TopLevelCommand(Command):
|
||||||
print("Attaching to", list_containers(containers))
|
print("Attaching to", list_containers(containers))
|
||||||
LogPrinter(containers, attach_params={'logs': True}, monochrome=monochrome).run()
|
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):
|
def ps(self, project, options):
|
||||||
"""
|
"""
|
||||||
List containers.
|
List containers.
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from fig.packages import six
|
||||||
|
|
||||||
|
|
||||||
class Container(object):
|
class Container(object):
|
||||||
"""
|
"""
|
||||||
|
@ -63,17 +65,20 @@ class Container(object):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def human_readable_ports(self):
|
def ports(self):
|
||||||
self.inspect_if_not_inspected()
|
self.inspect_if_not_inspected()
|
||||||
if not self.dictionary['NetworkSettings']['Ports']:
|
return self.dictionary['NetworkSettings']['Ports'] or {}
|
||||||
return ''
|
|
||||||
ports = []
|
@property
|
||||||
for private, public in list(self.dictionary['NetworkSettings']['Ports'].items()):
|
def human_readable_ports(self):
|
||||||
if public:
|
def format_port(private, public):
|
||||||
ports.append('%s->%s' % (public[0]['HostPort'], private))
|
if not public:
|
||||||
else:
|
return private
|
||||||
ports.append(private)
|
return '{HostIp}:{HostPort}->{private}'.format(
|
||||||
return ', '.join(ports)
|
private=private, **public[0])
|
||||||
|
|
||||||
|
return ', '.join(format_port(*item)
|
||||||
|
for item in sorted(six.iteritems(self.ports)))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def human_readable_state(self):
|
def human_readable_state(self):
|
||||||
|
@ -105,6 +110,10 @@ class Container(object):
|
||||||
self.inspect_if_not_inspected()
|
self.inspect_if_not_inspected()
|
||||||
return self.dictionary['State']['Running']
|
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):
|
def start(self, **options):
|
||||||
return self.client.start(self.id, **options)
|
return self.client.start(self.id, **options)
|
||||||
|
|
||||||
|
|
|
@ -78,9 +78,22 @@ class Service(object):
|
||||||
name = get_container_name(container)
|
name = get_container_name(container)
|
||||||
if not name or not is_valid_name(name, one_off):
|
if not name or not is_valid_name(name, one_off):
|
||||||
return False
|
return False
|
||||||
project, name, number = parse_name(name)
|
project, name, _number = parse_name(name)
|
||||||
return project == self.project and name == self.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):
|
def start(self, **options):
|
||||||
for c in self.containers(stopped=True):
|
for c in self.containers(stopped=True):
|
||||||
self.start_container_if_stopped(c, **options)
|
self.start_container_if_stopped(c, **options)
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
|
||||||
|
simple:
|
||||||
|
image: busybox:latest
|
||||||
|
command: /bin/sleep 300
|
||||||
|
ports:
|
||||||
|
- '3000'
|
||||||
|
- '9999:3001'
|
|
@ -1,10 +1,12 @@
|
||||||
from __future__ import absolute_import
|
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
|
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):
|
class CLITestCase(DockerClientTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -213,3 +215,17 @@ class CLITestCase(DockerClientTestCase):
|
||||||
self.command.scale(project, {'SERVICE=NUM': ['simple=0', 'another=0']})
|
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('simple').containers()), 0)
|
||||||
self.assertEqual(len(project.get_service('another').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), "")
|
||||||
|
|
|
@ -8,18 +8,28 @@ from fig.container import Container
|
||||||
|
|
||||||
|
|
||||||
class ContainerTest(unittest.TestCase):
|
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):
|
def test_from_ps(self):
|
||||||
container = Container.from_ps(None, {
|
container = Container.from_ps(None,
|
||||||
"Id":"abc",
|
self.container_dict,
|
||||||
"Image":"busybox:latest",
|
has_been_inspected=True)
|
||||||
"Command":"sleep 300",
|
|
||||||
"Created":1387384730,
|
|
||||||
"Status":"Up 8 seconds",
|
|
||||||
"Ports":None,
|
|
||||||
"SizeRw":0,
|
|
||||||
"SizeRootFs":0,
|
|
||||||
"Names":["/figtest_db_1"]
|
|
||||||
}, has_been_inspected=True)
|
|
||||||
self.assertEqual(container.dictionary, {
|
self.assertEqual(container.dictionary, {
|
||||||
"Id": "abc",
|
"Id": "abc",
|
||||||
"Image":"busybox:latest",
|
"Image":"busybox:latest",
|
||||||
|
@ -42,35 +52,21 @@ class ContainerTest(unittest.TestCase):
|
||||||
})
|
})
|
||||||
|
|
||||||
def test_number(self):
|
def test_number(self):
|
||||||
container = Container.from_ps(None, {
|
container = Container.from_ps(None,
|
||||||
"Id":"abc",
|
self.container_dict,
|
||||||
"Image":"busybox:latest",
|
has_been_inspected=True)
|
||||||
"Command":"sleep 300",
|
|
||||||
"Created":1387384730,
|
|
||||||
"Status":"Up 8 seconds",
|
|
||||||
"Ports":None,
|
|
||||||
"SizeRw":0,
|
|
||||||
"SizeRootFs":0,
|
|
||||||
"Names":["/figtest_db_1"]
|
|
||||||
}, has_been_inspected=True)
|
|
||||||
self.assertEqual(container.number, 1)
|
self.assertEqual(container.number, 1)
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
container = Container.from_ps(None, {
|
container = Container.from_ps(None,
|
||||||
"Id":"abc",
|
self.container_dict,
|
||||||
"Image":"busybox:latest",
|
has_been_inspected=True)
|
||||||
"Command":"sleep 300",
|
|
||||||
"Names":["/figtest_db_1"]
|
|
||||||
}, has_been_inspected=True)
|
|
||||||
self.assertEqual(container.name, "figtest_db_1")
|
self.assertEqual(container.name, "figtest_db_1")
|
||||||
|
|
||||||
def test_name_without_project(self):
|
def test_name_without_project(self):
|
||||||
container = Container.from_ps(None, {
|
container = Container.from_ps(None,
|
||||||
"Id":"abc",
|
self.container_dict,
|
||||||
"Image":"busybox:latest",
|
has_been_inspected=True)
|
||||||
"Command":"sleep 300",
|
|
||||||
"Names":["/figtest_db_1"]
|
|
||||||
}, has_been_inspected=True)
|
|
||||||
self.assertEqual(container.name_without_project, "db_1")
|
self.assertEqual(container.name_without_project, "db_1")
|
||||||
|
|
||||||
def test_inspect_if_not_inspected(self):
|
def test_inspect_if_not_inspected(self):
|
||||||
|
@ -85,3 +81,27 @@ class ContainerTest(unittest.TestCase):
|
||||||
|
|
||||||
container.inspect_if_not_inspected()
|
container.inspect_if_not_inspected()
|
||||||
self.assertEqual(mock_client.inspect_container.call_count, 1)
|
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')
|
||||||
|
|
|
@ -5,6 +5,8 @@ import os
|
||||||
from .. import unittest
|
from .. import unittest
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
|
from fig.packages import docker
|
||||||
|
|
||||||
from fig import Service
|
from fig import Service
|
||||||
from fig.service import (
|
from fig.service import (
|
||||||
ConfigError,
|
ConfigError,
|
||||||
|
@ -97,14 +99,33 @@ class ServiceTest(unittest.TestCase):
|
||||||
|
|
||||||
def test_split_domainname_weird(self):
|
def test_split_domainname_weird(self):
|
||||||
service = Service('foo',
|
service = Service('foo',
|
||||||
hostname = 'name.sub',
|
hostname='name.sub',
|
||||||
domainname = 'domain.tld',
|
domainname='domain.tld',
|
||||||
)
|
)
|
||||||
service.next_container_name = lambda x: 'foo'
|
service.next_container_name = lambda x: 'foo'
|
||||||
opts = service._get_container_create_options({})
|
opts = service._get_container_create_options({})
|
||||||
self.assertEqual(opts['hostname'], 'name.sub', 'hostname')
|
self.assertEqual(opts['hostname'], 'name.sub', 'hostname')
|
||||||
self.assertEqual(opts['domainname'], 'domain.tld', 'domainname')
|
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):
|
class ServiceVolumesTest(unittest.TestCase):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue