Skip to content

Commit

Permalink
Replace sequential container indexes with randomly generated IDs
Browse files Browse the repository at this point in the history
Signed-off-by: Joffrey F <joffrey@docker.com>
  • Loading branch information
shin- committed Aug 15, 2018
1 parent 901ee4e commit 69be0a3
Show file tree
Hide file tree
Showing 11 changed files with 228 additions and 172 deletions.
14 changes: 8 additions & 6 deletions compose/cli/main.py
Expand Up @@ -474,15 +474,16 @@ def exec_command(self, options):
-u, --user USER Run the command as this user.
-T Disable pseudo-tty allocation. By default `docker-compose exec`
allocates a TTY.
--index=index index of the container if there are multiple
instances of a service [default: 1]
--index=index "index" of the container if there are multiple
instances of a service. If missing, Compose will pick an
arbitrary container.
-e, --env KEY=VAL Set environment variables (can be used multiple times,
not supported in API < 1.25)
-w, --workdir DIR Path to workdir directory for this command.
"""
environment = Environment.from_env_file(self.project_dir)
use_cli = not environment.get_boolean('COMPOSE_INTERACTIVE_NO_CLI')
index = int(options.get('--index'))
index = options.get('--index')
service = self.project.get_service(options['SERVICE'])
detach = options.get('--detach')

Expand Down Expand Up @@ -659,10 +660,11 @@ def port(self, options):
Options:
--protocol=proto tcp or udp [default: tcp]
--index=index index of the container if there are multiple
instances of a service [default: 1]
--index=index "index" of the container if there are multiple
instances of a service. If missing, Compose will pick an
arbitrary container.
"""
index = int(options.get('--index'))
index = options.get('--index')
service = self.project.get_service(options['SERVICE'])
try:
container = service.get_container(number=index)
Expand Down
9 changes: 7 additions & 2 deletions compose/container.py
Expand Up @@ -10,6 +10,7 @@
from .const import LABEL_PROJECT
from .const import LABEL_SERVICE
from .const import LABEL_VERSION
from .utils import truncate_id
from .version import ComposeVersion


Expand Down Expand Up @@ -80,7 +81,7 @@ def service(self):
@property
def name_without_project(self):
if self.name.startswith('{0}_{1}'.format(self.project, self.service)):
return '{0}_{1}'.format(self.service, self.number)
return '{0}_{1}'.format(self.service, self.short_number)
else:
return self.name

Expand All @@ -90,7 +91,11 @@ def number(self):
if not number:
raise ValueError("Container {0} does not have a {1} label".format(
self.short_id, LABEL_CONTAINER_NUMBER))
return int(number)
return number

@property
def short_number(self):
return truncate_id(self.number)

@property
def ports(self):
Expand Down
38 changes: 16 additions & 22 deletions compose/service.py
@@ -1,7 +1,6 @@
from __future__ import absolute_import
from __future__ import unicode_literals

import itertools
import logging
import os
import re
Expand Down Expand Up @@ -49,9 +48,11 @@
from .parallel import parallel_execute
from .progress_stream import stream_output
from .progress_stream import StreamOutputError
from .utils import generate_random_id
from .utils import json_hash
from .utils import parse_bytes
from .utils import parse_seconds_float
from .utils import truncate_id


log = logging.getLogger(__name__)
Expand Down Expand Up @@ -215,13 +216,17 @@ def containers(self, stopped=False, one_off=False, filters={}, labels=None):
)
)

def get_container(self, number=1):
def get_container(self, number=None):
"""Return a :class:`compose.container.Container` for this service. The
container must be active, and match `number`.
"""

for container in self.containers(labels=['{0}={1}'.format(LABEL_CONTAINER_NUMBER, number)]):
return container
if number is not None and len(number) == 64:
for container in self.containers(labels=['{0}={1}'.format(LABEL_CONTAINER_NUMBER, number)]):
return container
else:
for container in self.containers():
if number is None or container.number.startswith(number):
return container

raise ValueError("No container found for %s_%s" % (self.name, number))

Expand Down Expand Up @@ -426,7 +431,6 @@ def _containers_have_diverged(self, containers):
return has_diverged

def _execute_convergence_create(self, scale, detached, start, project_services=None):
i = self._next_container_number()

def create_and_start(service, n):
container = service.create_container(number=n, quiet=True)
Expand All @@ -437,7 +441,9 @@ def create_and_start(service, n):
return container

containers, errors = parallel_execute(
[ServiceName(self.project, self.name, index) for index in range(i, i + scale)],
[ServiceName(self.project, self.name, number) for number in [
self._next_container_number() for _ in range(scale)
]],
lambda service_name: create_and_start(self, service_name.number),
lambda service_name: self.get_container_name(service_name.service, service_name.number),
"Creating"
Expand Down Expand Up @@ -568,7 +574,7 @@ def recreate_container(self, container, timeout=None, attach_logs=False, start_n
container.rename_to_tmp_name()
new_container = self.create_container(
previous_container=container if not renew_anonymous_volumes else None,
number=container.labels.get(LABEL_CONTAINER_NUMBER),
number=container.number,
quiet=True,
)
if attach_logs:
Expand Down Expand Up @@ -723,20 +729,8 @@ def get_link_names(self):
def get_volumes_from_names(self):
return [s.source.name for s in self.volumes_from if isinstance(s.source, Service)]

# TODO: this would benefit from github.com/docker/docker/pull/14699
# to remove the need to inspect every container
def _next_container_number(self, one_off=False):
containers = itertools.chain(
self._fetch_containers(
all=True,
filters={'label': self.labels(one_off=one_off)}
), self._fetch_containers(
all=True,
filters={'label': self.labels(one_off=one_off, legacy=True)}
)
)
numbers = [c.number for c in containers]
return 1 if not numbers else max(numbers) + 1
return generate_random_id()

def _fetch_containers(self, **fetch_options):
# Account for containers that might have been removed since we fetched
Expand Down Expand Up @@ -1370,7 +1364,7 @@ def build_container_name(project, service, number, one_off=False):
bits = [project.lstrip('-_'), service]
if one_off:
bits.append('run')
return '_'.join(bits + [str(number)])
return '_'.join(bits + [truncate_id(number)])


# Images
Expand Down
19 changes: 19 additions & 0 deletions compose/utils.py
Expand Up @@ -7,6 +7,7 @@
import json.decoder
import logging
import ntpath
import random

import six
from docker.errors import DockerException
Expand Down Expand Up @@ -151,3 +152,21 @@ def unquote_path(s):
if s[0] == '"' and s[-1] == '"':
return s[1:-1]
return s


def generate_random_id():
while True:
val = hex(random.getrandbits(32 * 8))[2:-1]
try:
int(truncate_id(val))
continue
except ValueError:
return val


def truncate_id(value):
if ':' in value:
value = value[value.index(':') + 1:]
if len(value) > 12:
return value[:12]
return value

0 comments on commit 69be0a3

Please sign in to comment.