-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Resolves #1066, use labels to identify containers
Signed-off-by: Daniel Nephin <[email protected]>
- Loading branch information
Showing
9 changed files
with
139 additions
and
103 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
from __future__ import unicode_literals | ||
from .service import Service # noqa:flake8 | ||
|
||
__version__ = '1.3.0dev' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
|
||
LABEL_CONTAINER_NUMBER = 'com.docker.compose.container-number' | ||
LABEL_ONE_OFF = 'com.docker.compose.oneoff' | ||
LABEL_PROJECT = 'com.docker.compose.project' | ||
LABEL_SERVICE = 'com.docker.compose.service' | ||
LABEL_VERSION = 'com.docker.compose.version' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,8 +10,16 @@ | |
from docker.errors import APIError | ||
from docker.utils import create_host_config, LogConfig | ||
|
||
from . import __version__ | ||
from .config import DOCKER_CONFIG_KEYS, merge_environment | ||
from .container import Container, get_container_name | ||
from .const import ( | ||
LABEL_CONTAINER_NUMBER, | ||
LABEL_ONE_OFF, | ||
LABEL_PROJECT, | ||
LABEL_SERVICE, | ||
LABEL_VERSION, | ||
) | ||
from .container import Container | ||
from .progress_stream import stream_output, StreamOutputError | ||
|
||
log = logging.getLogger(__name__) | ||
|
@@ -79,27 +87,17 @@ def __init__(self, name, client=None, project='default', links=None, external_li | |
|
||
def containers(self, stopped=False, one_off=False): | ||
return [Container.from_ps(self.client, container) | ||
for container in self.client.containers(all=stopped) | ||
if self.has_container(container, one_off=one_off)] | ||
|
||
def has_container(self, container, one_off=False): | ||
"""Return True if `container` was created to fulfill this service.""" | ||
name = get_container_name(container) | ||
if not name or not is_valid_name(name, one_off): | ||
return False | ||
project, name, _number = parse_name(name) | ||
return project == self.project and name == self.name | ||
for container in self.client.containers( | ||
all=stopped, | ||
filters={'label': self.labels(one_off=one_off)})] | ||
|
||
def get_container(self, number=1): | ||
"""Return a :class:`compose.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) | ||
labels = self.labels() + ['{0}={1}'.format(LABEL_CONTAINER_NUMBER, number)] | ||
for container in self.client.containers(filters={'label': labels}): | ||
return Container.from_ps(self.client, container) | ||
|
||
raise ValueError("No container found for %s_%s" % (self.name, number)) | ||
|
||
|
@@ -138,7 +136,6 @@ def scale(self, desired_num): | |
# Create enough containers | ||
containers = self.containers(stopped=True) | ||
while len(containers) < desired_num: | ||
log.info("Creating %s..." % self._next_container_name(containers)) | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
aanand
|
||
containers.append(self.create_container(detach=True)) | ||
|
||
running_containers = [] | ||
|
@@ -178,13 +175,15 @@ def create_container(self, | |
insecure_registry=False, | ||
do_build=True, | ||
previous_container=None, | ||
number=None, | ||
**override_options): | ||
""" | ||
Create a container for this service. If the image doesn't exist, attempt to pull | ||
it. | ||
""" | ||
container_options = self._get_container_create_options( | ||
override_options, | ||
number or self._next_container_number(one_off=one_off), | ||
one_off=one_off, | ||
previous_container=previous_container, | ||
) | ||
|
@@ -209,7 +208,6 @@ def recreate_containers(self, insecure_registry=False, do_build=True, **override | |
""" | ||
containers = self.containers(stopped=True) | ||
if not containers: | ||
log.info("Creating %s..." % self._next_container_name(containers)) | ||
container = self.create_container( | ||
insecure_registry=insecure_registry, | ||
do_build=do_build, | ||
|
@@ -256,6 +254,7 @@ def recreate_container(self, container, **override_options): | |
new_container = self.create_container( | ||
do_build=False, | ||
previous_container=container, | ||
number=container.labels.get(LABEL_CONTAINER_NUMBER), | ||
**override_options) | ||
self.start_container(new_container) | ||
container.remove() | ||
|
@@ -280,7 +279,6 @@ def start_or_create_containers( | |
containers = self.containers(stopped=True) | ||
|
||
if not containers: | ||
log.info("Creating %s..." % self._next_container_name(containers)) | ||
new_container = self.create_container( | ||
insecure_registry=insecure_registry, | ||
detach=detach, | ||
|
@@ -302,14 +300,19 @@ def get_net_name(self): | |
else: | ||
return | ||
|
||
def _next_container_name(self, all_containers, one_off=False): | ||
bits = [self.project, self.name] | ||
if one_off: | ||
bits.append('run') | ||
return '_'.join(bits + [str(self._next_container_number(all_containers))]) | ||
|
||
def _next_container_number(self, all_containers): | ||
numbers = [parse_name(c.name).number for c in all_containers] | ||
def get_container_name(self, number, one_off=False): | ||
# TODO: Implement issue #652 here | ||
return build_container_name(self.project, self.name, number, one_off) | ||
|
||
# TODO: this would benefit from github.com/docker/docker/pull/11943 | ||
# to remove the need to inspect every container | ||
def _next_container_number(self, one_off=False): | ||
numbers = [ | ||
Container.from_ps(self.client, container).number | ||
for container in self.client.containers( | ||
all=True, | ||
filters={'label': self.labels(one_off=one_off)}) | ||
] | ||
return 1 if not numbers else max(numbers) + 1 | ||
|
||
def _get_links(self, link_to_self): | ||
|
@@ -369,16 +372,15 @@ def _get_net(self): | |
def _get_container_create_options( | ||
self, | ||
override_options, | ||
number, | ||
one_off=False, | ||
previous_container=None): | ||
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['name'] = self._next_container_name( | ||
self.containers(stopped=True, one_off=one_off), | ||
one_off) | ||
container_options['name'] = self.get_container_name(number, one_off) | ||
|
||
# If a qualified hostname was given, split it into an | ||
# unqualified hostname and a domainname unless domainname | ||
|
@@ -419,6 +421,11 @@ def _get_container_create_options( | |
if self.can_be_built(): | ||
container_options['image'] = self.full_name | ||
|
||
container_options['labels'] = build_container_labels( | ||
container_options.get('labels', {}), | ||
self.labels(one_off=one_off), | ||
number) | ||
|
||
# Delete options which are only used when starting | ||
for key in DOCKER_START_KEYS: | ||
container_options.pop(key, None) | ||
|
@@ -520,6 +527,13 @@ def full_name(self): | |
""" | ||
return '%s_%s' % (self.project, self.name) | ||
|
||
def labels(self, one_off=False): | ||
return [ | ||
'{0}={1}'.format(LABEL_PROJECT, self.project), | ||
'{0}={1}'.format(LABEL_SERVICE, self.name), | ||
'{0}={1}'.format(LABEL_ONE_OFF, "True" if one_off else "False") | ||
] | ||
|
||
def can_be_scaled(self): | ||
for port in self.options.get('ports', []): | ||
if ':' in str(port): | ||
|
@@ -585,23 +599,19 @@ def merge_volume_bindings(volumes_option, previous_container): | |
return volume_bindings | ||
|
||
|
||
NAME_RE = re.compile(r'^([^_]+)_([^_]+)_(run_)?(\d+)$') | ||
|
||
|
||
def is_valid_name(name, one_off=False): | ||
match = NAME_RE.match(name) | ||
if match is None: | ||
return False | ||
def build_container_name(project, service, number, one_off=False): | ||
bits = [project, service] | ||
if one_off: | ||
return match.group(3) == 'run_' | ||
else: | ||
return match.group(3) is None | ||
bits.append('run') | ||
return '_'.join(bits + [str(number)]) | ||
|
||
|
||
def parse_name(name): | ||
match = NAME_RE.match(name) | ||
(project, service_name, _, suffix) = match.groups() | ||
return ServiceName(project, service_name, int(suffix)) | ||
def build_container_labels(label_options, service_labels, number, one_off=False): | ||
labels = label_options or {} | ||
labels.update(label.split('=', 1) for label in service_labels) | ||
labels[LABEL_CONTAINER_NUMBER] = str(number) | ||
labels[LABEL_VERSION] = __version__ | ||
return labels | ||
|
||
|
||
def parse_restart_spec(restart_config): | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Why was this log removed ?