mirror of https://github.com/docker/docs.git
Merge pull request #236 from rail44/feature-support-volumes-from
Support volumes_from option
This commit is contained in:
commit
d04b1724ec
|
@ -2,6 +2,8 @@ from __future__ import unicode_literals
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
import logging
|
import logging
|
||||||
from .service import Service
|
from .service import Service
|
||||||
|
from .container import Container
|
||||||
|
from .packages.docker.errors import APIError
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -18,11 +20,13 @@ def sort_service_dicts(services):
|
||||||
if n['name'] in temporary_marked:
|
if n['name'] in temporary_marked:
|
||||||
if n['name'] in get_service_names(n.get('links', [])):
|
if n['name'] in get_service_names(n.get('links', [])):
|
||||||
raise DependencyError('A service can not link to itself: %s' % n['name'])
|
raise DependencyError('A service can not link to itself: %s' % n['name'])
|
||||||
|
if n['name'] in n.get('volumes_from', []):
|
||||||
|
raise DependencyError('A service can not mount itself as volume: %s' % n['name'])
|
||||||
else:
|
else:
|
||||||
raise DependencyError('Circular import between %s' % ' and '.join(temporary_marked))
|
raise DependencyError('Circular import between %s' % ' and '.join(temporary_marked))
|
||||||
if n in unmarked:
|
if n in unmarked:
|
||||||
temporary_marked.add(n['name'])
|
temporary_marked.add(n['name'])
|
||||||
dependents = [m for m in services if n['name'] in get_service_names(m.get('links', []))]
|
dependents = [m for m in services if (n['name'] in get_service_names(m.get('links', []))) or (n['name'] in m.get('volumes_from', []))]
|
||||||
for m in dependents:
|
for m in dependents:
|
||||||
visit(m)
|
visit(m)
|
||||||
temporary_marked.remove(n['name'])
|
temporary_marked.remove(n['name'])
|
||||||
|
@ -50,22 +54,10 @@ class Project(object):
|
||||||
"""
|
"""
|
||||||
project = cls(name, [], client)
|
project = cls(name, [], client)
|
||||||
for service_dict in sort_service_dicts(service_dicts):
|
for service_dict in sort_service_dicts(service_dicts):
|
||||||
# Reference links by object
|
links = project.get_links(service_dict)
|
||||||
links = []
|
volumes_from = project.get_volumes_from(service_dict)
|
||||||
if 'links' in service_dict:
|
|
||||||
for link in service_dict.get('links', []):
|
|
||||||
if ':' in link:
|
|
||||||
service_name, link_name = link.split(':', 1)
|
|
||||||
else:
|
|
||||||
service_name, link_name = link, None
|
|
||||||
try:
|
|
||||||
links.append((project.get_service(service_name), link_name))
|
|
||||||
except NoSuchService:
|
|
||||||
raise ConfigurationError('Service "%s" has a link to service "%s" which does not exist.' % (service_dict['name'], service_name))
|
|
||||||
|
|
||||||
del service_dict['links']
|
project.services.append(Service(client=client, project=name, links=links, volumes_from=volumes_from, **service_dict))
|
||||||
|
|
||||||
project.services.append(Service(client=client, project=name, links=links, **service_dict))
|
|
||||||
return project
|
return project
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -119,6 +111,37 @@ class Project(object):
|
||||||
[uniques.append(s) for s in services if s not in uniques]
|
[uniques.append(s) for s in services if s not in uniques]
|
||||||
return uniques
|
return uniques
|
||||||
|
|
||||||
|
def get_links(self, service_dict):
|
||||||
|
links = []
|
||||||
|
if 'links' in service_dict:
|
||||||
|
for link in service_dict.get('links', []):
|
||||||
|
if ':' in link:
|
||||||
|
service_name, link_name = link.split(':', 1)
|
||||||
|
else:
|
||||||
|
service_name, link_name = link, None
|
||||||
|
try:
|
||||||
|
links.append((self.get_service(service_name), link_name))
|
||||||
|
except NoSuchService:
|
||||||
|
raise ConfigurationError('Service "%s" has a link to service "%s" which does not exist.' % (service_dict['name'], service_name))
|
||||||
|
del service_dict['links']
|
||||||
|
return links
|
||||||
|
|
||||||
|
def get_volumes_from(self, service_dict):
|
||||||
|
volumes_from = []
|
||||||
|
if 'volumes_from' in service_dict:
|
||||||
|
for volume_name in service_dict.get('volumes_from', []):
|
||||||
|
try:
|
||||||
|
service = self.get_service(volume_name)
|
||||||
|
volumes_from.append(service)
|
||||||
|
except NoSuchService:
|
||||||
|
try:
|
||||||
|
container = Container.from_id(client, volume_name)
|
||||||
|
volumes_from.append(Container.from_id(client, volume_name))
|
||||||
|
except APIError:
|
||||||
|
raise ConfigurationError('Service "%s" mounts volumes from "%s", which is not the name of a service or container.' % (service_dict['name'], volume_name))
|
||||||
|
del service_dict['volumes_from']
|
||||||
|
return volumes_from
|
||||||
|
|
||||||
def start(self, service_names=None, **options):
|
def start(self, service_names=None, **options):
|
||||||
for service in self.get_services(service_names):
|
for service in self.get_services(service_names):
|
||||||
service.start(**options)
|
service.start(**options)
|
||||||
|
|
|
@ -11,7 +11,7 @@ from .progress_stream import stream_output, StreamOutputError
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
DOCKER_CONFIG_KEYS = ['image', 'command', 'hostname', 'user', 'detach', 'stdin_open', 'tty', 'mem_limit', 'ports', 'environment', 'dns', 'volumes', 'volumes_from', 'entrypoint', 'privileged', 'net']
|
DOCKER_CONFIG_KEYS = ['image', 'command', 'hostname', 'user', 'detach', 'stdin_open', 'tty', 'mem_limit', 'ports', 'environment', 'dns', 'volumes', 'entrypoint', 'privileged', 'volumes_from', 'net']
|
||||||
DOCKER_CONFIG_HINTS = {
|
DOCKER_CONFIG_HINTS = {
|
||||||
'link' : 'links',
|
'link' : 'links',
|
||||||
'port' : 'ports',
|
'port' : 'ports',
|
||||||
|
@ -39,7 +39,7 @@ class ConfigError(ValueError):
|
||||||
|
|
||||||
|
|
||||||
class Service(object):
|
class Service(object):
|
||||||
def __init__(self, name, client=None, project='default', links=[], **options):
|
def __init__(self, name, client=None, project='default', links=[], volumes_from=[], **options):
|
||||||
if not re.match('^%s+$' % VALID_NAME_CHARS, name):
|
if not re.match('^%s+$' % VALID_NAME_CHARS, name):
|
||||||
raise ConfigError('Invalid service name "%s" - only %s are allowed' % (name, VALID_NAME_CHARS))
|
raise ConfigError('Invalid service name "%s" - only %s are allowed' % (name, VALID_NAME_CHARS))
|
||||||
if not re.match('^%s+$' % VALID_NAME_CHARS, project):
|
if not re.match('^%s+$' % VALID_NAME_CHARS, project):
|
||||||
|
@ -60,6 +60,7 @@ class Service(object):
|
||||||
self.client = client
|
self.client = client
|
||||||
self.project = project
|
self.project = project
|
||||||
self.links = links or []
|
self.links = links or []
|
||||||
|
self.volumes_from = volumes_from or []
|
||||||
self.options = options
|
self.options = options
|
||||||
|
|
||||||
def containers(self, stopped=False, one_off=False):
|
def containers(self, stopped=False, one_off=False):
|
||||||
|
@ -190,7 +191,7 @@ class Service(object):
|
||||||
|
|
||||||
options = dict(override_options)
|
options = dict(override_options)
|
||||||
new_container = self.create_container(**options)
|
new_container = self.create_container(**options)
|
||||||
self.start_container(new_container, volumes_from=intermediate_container.id)
|
self.start_container(new_container, intermediate_container=intermediate_container)
|
||||||
|
|
||||||
intermediate_container.remove()
|
intermediate_container.remove()
|
||||||
|
|
||||||
|
@ -203,7 +204,7 @@ class Service(object):
|
||||||
log.info("Starting %s..." % container.name)
|
log.info("Starting %s..." % container.name)
|
||||||
return self.start_container(container, **options)
|
return self.start_container(container, **options)
|
||||||
|
|
||||||
def start_container(self, container=None, volumes_from=None, **override_options):
|
def start_container(self, container=None, intermediate_container=None,**override_options):
|
||||||
if container is None:
|
if container is None:
|
||||||
container = self.create_container(**override_options)
|
container = self.create_container(**override_options)
|
||||||
|
|
||||||
|
@ -235,7 +236,7 @@ class Service(object):
|
||||||
links=self._get_links(link_to_self=override_options.get('one_off', False)),
|
links=self._get_links(link_to_self=override_options.get('one_off', False)),
|
||||||
port_bindings=port_bindings,
|
port_bindings=port_bindings,
|
||||||
binds=volume_bindings,
|
binds=volume_bindings,
|
||||||
volumes_from=volumes_from,
|
volumes_from=self._get_volumes_from(intermediate_container),
|
||||||
privileged=privileged,
|
privileged=privileged,
|
||||||
network_mode=net,
|
network_mode=net,
|
||||||
)
|
)
|
||||||
|
@ -282,6 +283,20 @@ class Service(object):
|
||||||
links.append((container.name, container.name_without_project))
|
links.append((container.name, container.name_without_project))
|
||||||
return links
|
return links
|
||||||
|
|
||||||
|
def _get_volumes_from(self, intermediate_container=None):
|
||||||
|
volumes_from = []
|
||||||
|
for v in self.volumes_from:
|
||||||
|
if isinstance(v, Service):
|
||||||
|
for container in v.containers(stopped=True):
|
||||||
|
volumes_from.append(container.id)
|
||||||
|
elif isinstance(v, Container):
|
||||||
|
volumes_from.append(v.id)
|
||||||
|
|
||||||
|
if intermediate_container:
|
||||||
|
volumes_from.append(intermediate_container.id)
|
||||||
|
|
||||||
|
return volumes_from
|
||||||
|
|
||||||
def _get_container_create_options(self, override_options, one_off=False):
|
def _get_container_create_options(self, override_options, one_off=False):
|
||||||
container_options = dict((k, self.options[k]) for k in DOCKER_CONFIG_KEYS if k in self.options)
|
container_options = dict((k, self.options[k]) for k in DOCKER_CONFIG_KEYS if k in self.options)
|
||||||
container_options.update(override_options)
|
container_options.update(override_options)
|
||||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
from fig import Service
|
from fig import Service
|
||||||
from fig.service import CannotBeScaledError
|
from fig.service import CannotBeScaledError
|
||||||
|
from fig.container import Container
|
||||||
from fig.packages.docker.errors import APIError
|
from fig.packages.docker.errors import APIError
|
||||||
from .testcases import DockerClientTestCase
|
from .testcases import DockerClientTestCase
|
||||||
|
|
||||||
|
@ -96,6 +97,16 @@ class ServiceTest(DockerClientTestCase):
|
||||||
service.start_container(container)
|
service.start_container(container)
|
||||||
self.assertIn('/host-tmp', container.inspect()['Volumes'])
|
self.assertIn('/host-tmp', container.inspect()['Volumes'])
|
||||||
|
|
||||||
|
def test_create_container_with_volumes_from(self):
|
||||||
|
volume_service = self.create_service('data')
|
||||||
|
volume_container_1 = volume_service.create_container()
|
||||||
|
volume_container_2 = Container.create(self.client, image='busybox:latest', command=["/bin/sleep", "300"])
|
||||||
|
host_service = self.create_service('host', volumes_from=[volume_service, volume_container_2])
|
||||||
|
host_container = host_service.create_container()
|
||||||
|
host_service.start_container(host_container)
|
||||||
|
self.assertIn(volume_container_1.id, host_container.inspect()['HostConfig']['VolumesFrom'])
|
||||||
|
self.assertIn(volume_container_2.id, host_container.inspect()['HostConfig']['VolumesFrom'])
|
||||||
|
|
||||||
def test_recreate_containers(self):
|
def test_recreate_containers(self):
|
||||||
service = self.create_service(
|
service = self.create_service(
|
||||||
'db',
|
'db',
|
||||||
|
@ -127,6 +138,7 @@ class ServiceTest(DockerClientTestCase):
|
||||||
self.assertIn('FOO=2', new_container.dictionary['Config']['Env'])
|
self.assertIn('FOO=2', new_container.dictionary['Config']['Env'])
|
||||||
self.assertEqual(new_container.name, 'figtest_db_1')
|
self.assertEqual(new_container.name, 'figtest_db_1')
|
||||||
self.assertEqual(new_container.inspect()['Volumes']['/var/db'], volume_path)
|
self.assertEqual(new_container.inspect()['Volumes']['/var/db'], volume_path)
|
||||||
|
self.assertIn(intermediate_container.id, new_container.dictionary['HostConfig']['VolumesFrom'])
|
||||||
|
|
||||||
self.assertEqual(len(self.client.containers(all=True)), num_containers_before)
|
self.assertEqual(len(self.client.containers(all=True)), num_containers_before)
|
||||||
self.assertNotEqual(old_container.id, new_container.id)
|
self.assertNotEqual(old_container.id, new_container.id)
|
||||||
|
|
|
@ -30,12 +30,19 @@ class ProjectTest(unittest.TestCase):
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'db',
|
'name': 'db',
|
||||||
'image': 'busybox:latest'
|
'image': 'busybox:latest',
|
||||||
|
'volumes_from': ['volume']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'volume',
|
||||||
|
'image': 'busybox:latest',
|
||||||
|
'volumes': ['/tmp'],
|
||||||
}
|
}
|
||||||
], None)
|
], None)
|
||||||
|
|
||||||
self.assertEqual(project.services[0].name, 'db')
|
self.assertEqual(project.services[0].name, 'volume')
|
||||||
self.assertEqual(project.services[1].name, 'web')
|
self.assertEqual(project.services[1].name, 'db')
|
||||||
|
self.assertEqual(project.services[2].name, 'web')
|
||||||
|
|
||||||
def test_from_config(self):
|
def test_from_config(self):
|
||||||
project = Project.from_config('figtest', {
|
project = Project.from_config('figtest', {
|
||||||
|
|
Loading…
Reference in New Issue