-
Notifications
You must be signed in to change notification settings - Fork 5.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Additional validation for container volumes and ports. #442
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
from __future__ import unicode_literals | ||
from __future__ import absolute_import | ||
from collections import namedtuple | ||
from .packages.docker.errors import APIError | ||
import logging | ||
import re | ||
|
@@ -39,6 +40,9 @@ class ConfigError(ValueError): | |
pass | ||
|
||
|
||
VolumeSpec = namedtuple('VolumeSpec', 'external internal mode') | ||
|
||
|
||
class Service(object): | ||
def __init__(self, name, client=None, project='default', links=None, volumes_from=None, **options): | ||
if not re.match('^%s+$' % VALID_NAME_CHARS, name): | ||
|
@@ -214,37 +218,22 @@ def start_container_if_stopped(self, container, **options): | |
return self.start_container(container, **options) | ||
|
||
def start_container(self, container=None, intermediate_container=None, **override_options): | ||
if container is None: | ||
container = self.create_container(**override_options) | ||
|
||
options = self.options.copy() | ||
options.update(override_options) | ||
|
||
port_bindings = {} | ||
container = container or self.create_container(**override_options) | ||
options = dict(self.options, **override_options) | ||
ports = dict(split_port(port) for port in options.get('ports') or []) | ||
|
||
if options.get('ports', None) is not None: | ||
for port in options['ports']: | ||
internal_port, external_port = split_port(port) | ||
port_bindings[internal_port] = external_port | ||
|
||
volume_bindings = {} | ||
|
||
if options.get('volumes', None) is not None: | ||
for volume in options['volumes']: | ||
if ':' in volume: | ||
external_dir, internal_dir = volume.split(':') | ||
volume_bindings[os.path.abspath(external_dir)] = { | ||
'bind': internal_dir, | ||
'ro': False, | ||
} | ||
volume_bindings = dict( | ||
build_volume_binding(parse_volume_spec(volume)) | ||
for volume in options.get('volumes') or [] | ||
if ':' in volume) | ||
|
||
privileged = options.get('privileged', False) | ||
net = options.get('net', 'bridge') | ||
dns = options.get('dns', None) | ||
|
||
container.start( | ||
links=self._get_links(link_to_self=override_options.get('one_off', False)), | ||
port_bindings=port_bindings, | ||
links=self._get_links(link_to_self=options.get('one_off', False)), | ||
port_bindings=ports, | ||
binds=volume_bindings, | ||
volumes_from=self._get_volumes_from(intermediate_container), | ||
privileged=privileged, | ||
|
@@ -256,7 +245,7 @@ def start_container(self, container=None, intermediate_container=None, **overrid | |
def start_or_create_containers(self): | ||
containers = self.containers(stopped=True) | ||
|
||
if len(containers) == 0: | ||
if not containers: | ||
log.info("Creating %s..." % self.next_container_name()) | ||
new_container = self.create_container() | ||
return [self.start_container(new_container)] | ||
|
@@ -338,7 +327,9 @@ def _get_container_create_options(self, override_options, one_off=False): | |
container_options['ports'] = ports | ||
|
||
if 'volumes' in container_options: | ||
container_options['volumes'] = dict((split_volume(v)[1], {}) for v in container_options['volumes']) | ||
container_options['volumes'] = dict( | ||
(parse_volume_spec(v).internal, {}) | ||
for v in container_options['volumes']) | ||
|
||
if 'environment' in container_options: | ||
if isinstance(container_options['environment'], list): | ||
|
@@ -433,32 +424,47 @@ def get_container_name(container): | |
return name[1:] | ||
|
||
|
||
def split_volume(v): | ||
""" | ||
If v is of the format EXTERNAL:INTERNAL, returns (EXTERNAL, INTERNAL). | ||
If v is of the format INTERNAL, returns (None, INTERNAL). | ||
""" | ||
if ':' in v: | ||
return v.split(':', 1) | ||
else: | ||
return (None, v) | ||
def parse_volume_spec(volume_config): | ||
parts = volume_config.split(':') | ||
if len(parts) > 3: | ||
raise ConfigError("Volume %s has incorrect format, should be " | ||
"external:internal[:mode]" % volume_config) | ||
|
||
if len(parts) == 1: | ||
return VolumeSpec(None, parts[0], 'rw') | ||
|
||
if len(parts) == 2: | ||
parts.append('rw') | ||
|
||
external, internal, mode = parts | ||
if mode not in ('rw', 'ro'): | ||
raise ConfigError("Volume %s has invalid mode (%s), should be " | ||
"one of: rw, ro." % (volume_config, mode)) | ||
|
||
return VolumeSpec(external, internal, mode) | ||
|
||
|
||
def build_volume_binding(volume_spec): | ||
internal = {'bind': volume_spec.internal, 'ro': volume_spec.mode == 'ro'} | ||
external = os.path.expanduser(volume_spec.external) | ||
return os.path.abspath(os.path.expandvars(external)), internal | ||
|
||
|
||
def split_port(port): | ||
port = str(port) | ||
external_ip = None | ||
if ':' in port: | ||
external_port, internal_port = port.rsplit(':', 1) | ||
if ':' in external_port: | ||
external_ip, external_port = external_port.split(':', 1) | ||
else: | ||
external_port, internal_port = (None, port) | ||
if external_ip: | ||
if external_port: | ||
external_port = (external_ip, external_port) | ||
else: | ||
external_port = (external_ip,) | ||
return internal_port, external_port | ||
parts = str(port).split(':') | ||
if not 1 <= len(parts) <= 3: | ||
raise ConfigError('Invalid port "%s", should be ' | ||
'[[remote_ip:]remote_port:]port[/protocol]' % port) | ||
|
||
if len(parts) == 1: | ||
internal_port, = parts | ||
return internal_port, None | ||
if len(parts) == 2: | ||
external_port, internal_port = parts | ||
return internal_port, external_port | ||
|
||
external_ip, external_port, internal_port = parts | ||
return internal_port, (external_ip, external_port or None) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This list-fiddling stuff is really confusing. Could we bring back the local variables, so I can see what's being returned in each case? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I'm happy to make some changes here. I think in order to validate the string properly, we still need to make it a list, but additional local variables I can add. |
||
|
||
def split_env(env): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've seen multiple people confused about this so I tried to clarify that this is only an issue on OSX, not linux.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1