Skip to content

Commit

Permalink
Merge branch 'master' into 1.29.x
Browse files Browse the repository at this point in the history
  • Loading branch information
Anca Iordache committed Apr 13, 2021
2 parents 0773730 + adae403 commit 575d676
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 165 deletions.
5 changes: 3 additions & 2 deletions compose/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def main(): # noqa: C901
if not IS_LINUX_PLATFORM and command == 'help':
print("\nDocker Compose is now in the Docker CLI, try `docker compose` help")
except (KeyboardInterrupt, signals.ShutdownException):
exit_with_metrics(command, "Aborting.", status=Status.FAILURE)
exit_with_metrics(command, "Aborting.", status=Status.CANCELED)
except (UserError, NoSuchService, ConfigurationError,
ProjectError, OperationFailedError) as e:
exit_with_metrics(command, e.msg, status=Status.FAILURE)
Expand All @@ -103,7 +103,8 @@ def main(): # noqa: C901
commands = "\n".join(parse_doc_section("commands:", getdoc(e.supercommand)))
if not IS_LINUX_PLATFORM:
commands += "\n\nDocker Compose is now in the Docker CLI, try `docker compose`"
exit_with_metrics(e.command, "No such command: {}\n\n{}".format(e.command, commands))
exit_with_metrics("", log_msg="No such command: {}\n\n{}".format(
e.command, commands), status=Status.FAILURE)
except (errors.ConnectionError, StreamParseError):
exit_with_metrics(command, status=Status.FAILURE)
except SystemExit as e:
Expand Down
85 changes: 0 additions & 85 deletions compose/cli/scan_suggest.py

This file was deleted.

1 change: 1 addition & 0 deletions compose/config/interpolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ class ConversionMap:
service_path('healthcheck', 'disable'): to_boolean,
service_path('deploy', 'labels', PATH_JOKER): to_str,
service_path('deploy', 'replicas'): to_int,
service_path('deploy', 'placement', 'max_replicas_per_node'): to_int,
service_path('deploy', 'resources', 'limits', "cpus"): to_float,
service_path('deploy', 'update_config', 'parallelism'): to_int,
service_path('deploy', 'update_config', 'max_failure_ratio'): to_float,
Expand Down
2 changes: 1 addition & 1 deletion compose/metrics/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def __init__(self, command,
context_type=None, status=Status.SUCCESS,
source=MetricsSource.CLI, uri=None):
super().__init__()
self.command = "compose " + command if command else "compose --help"
self.command = ("compose " + command).strip() if command else "compose --help"
self.context = context_type or ContextAPI.get_current_context().context_type or 'moby'
self.source = source
self.status = status.value
Expand Down
11 changes: 0 additions & 11 deletions compose/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

from . import parallel
from .cli.errors import UserError
from .cli.scan_suggest import display_scan_suggest_msg
from .config import ConfigurationError
from .config.config import V1
from .config.sort_services import get_container_name_from_network_mode
Expand Down Expand Up @@ -519,9 +518,6 @@ def build_service(service):
for service in services:
build_service(service)

if services:
display_scan_suggest_msg()

def create(
self,
service_names=None,
Expand Down Expand Up @@ -664,15 +660,8 @@ def up(self,
service_names,
include_deps=start_deps)

must_build = False
for svc in services:
if svc.must_build(do_build=do_build):
must_build = True
svc.ensure_image_exists(do_build=do_build, silent=silent, cli=cli)

if must_build:
display_scan_suggest_msg()

plans = self._get_convergence_plans(
services,
strategy,
Expand Down
133 changes: 68 additions & 65 deletions compose/service.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import enum
import itertools
import json
import logging
import os
import re
Expand Down Expand Up @@ -368,24 +367,6 @@ def ensure_image_exists(self, do_build=BuildAction.none, silent=False, cli=False
"rebuild this image you must use `docker-compose build` or "
"`docker-compose up --build`.".format(self.name))

def must_build(self, do_build=BuildAction.none):
if self.can_be_built() and do_build == BuildAction.force:
return True

try:
self.image()
return False
except NoSuchImageError:
pass

if not self.can_be_built():
return False

if do_build == BuildAction.skip:
return False

return True

def get_image_registry_data(self):
try:
return self.client.inspect_distribution(self.image_name)
Expand Down Expand Up @@ -732,6 +713,7 @@ def image_id():
'image_id': image_id(),
'links': self.get_link_names(),
'net': self.network_mode.id,
'ipc_mode': self.ipc_mode.mode,
'networks': self.networks,
'secrets': self.secrets,
'volumes_from': [
Expand Down Expand Up @@ -1125,8 +1107,9 @@ def build(self, no_cache=False, pull=False, force_rm=False, memory=None, build_a
'Impossible to perform platform-targeted builds for API version < 1.35'
)

builder = self.client if not cli else _CLIBuilder(progress)
build_output = builder.build(
builder = _ClientBuilder(self.client) if not cli else _CLIBuilder(progress)
return builder.build(
service=self,
path=path,
tag=self.image_name,
rm=rm,
Expand All @@ -1147,30 +1130,7 @@ def build(self, no_cache=False, pull=False, force_rm=False, memory=None, build_a
gzip=gzip,
isolation=build_opts.get('isolation', self.options.get('isolation', None)),
platform=self.platform,
)

try:
all_events = list(stream_output(build_output, output_stream))
except StreamOutputError as e:
raise BuildError(self, str(e))

# Ensure the HTTP connection is not reused for another
# streaming command, as the Docker daemon can sometimes
# complain about it
self.client.close()

image_id = None

for event in all_events:
if 'stream' in event:
match = re.search(r'Successfully built ([0-9a-f]+)', event.get('stream', ''))
if match:
image_id = match.group(1)

if image_id is None:
raise BuildError(self, event if all_events else 'Unknown')

return image_id
output_stream=output_stream)

def get_cache_from(self, build_opts):
cache_from = build_opts.get('cache_from', None)
Expand Down Expand Up @@ -1827,20 +1787,77 @@ def rewrite_build_path(path):
return path


class _ClientBuilder:
def __init__(self, client):
self.client = client

def build(self, service, path, tag=None, quiet=False, fileobj=None,
nocache=False, rm=False, timeout=None,
custom_context=False, encoding=None, pull=False,
forcerm=False, dockerfile=None, container_limits=None,
decode=False, buildargs=None, gzip=False, shmsize=None,
labels=None, cache_from=None, target=None, network_mode=None,
squash=None, extra_hosts=None, platform=None, isolation=None,
use_config_proxy=True, output_stream=sys.stdout):
build_output = self.client.build(
path=path,
tag=tag,
nocache=nocache,
rm=rm,
pull=pull,
forcerm=forcerm,
dockerfile=dockerfile,
labels=labels,
cache_from=cache_from,
buildargs=buildargs,
network_mode=network_mode,
target=target,
shmsize=shmsize,
extra_hosts=extra_hosts,
container_limits=container_limits,
gzip=gzip,
isolation=isolation,
platform=platform)

try:
all_events = list(stream_output(build_output, output_stream))
except StreamOutputError as e:
raise BuildError(service, str(e))

# Ensure the HTTP connection is not reused for another
# streaming command, as the Docker daemon can sometimes
# complain about it
self.client.close()

image_id = None

for event in all_events:
if 'stream' in event:
match = re.search(r'Successfully built ([0-9a-f]+)', event.get('stream', ''))
if match:
image_id = match.group(1)

if image_id is None:
raise BuildError(service, event if all_events else 'Unknown')

return image_id


class _CLIBuilder:
def __init__(self, progress):
self._progress = progress

def build(self, path, tag=None, quiet=False, fileobj=None,
def build(self, service, path, tag=None, quiet=False, fileobj=None,
nocache=False, rm=False, timeout=None,
custom_context=False, encoding=None, pull=False,
forcerm=False, dockerfile=None, container_limits=None,
decode=False, buildargs=None, gzip=False, shmsize=None,
labels=None, cache_from=None, target=None, network_mode=None,
squash=None, extra_hosts=None, platform=None, isolation=None,
use_config_proxy=True):
use_config_proxy=True, output_stream=sys.stdout):
"""
Args:
service (str): Service to be built
path (str): Path to the directory containing the Dockerfile
buildargs (dict): A dictionary of build arguments
cache_from (:py:class:`list`): A list of images used for build
Expand Down Expand Up @@ -1889,6 +1906,7 @@ def build(self, path, tag=None, quiet=False, fileobj=None,
configuration file (``~/.docker/config.json`` by default)
contains a proxy configuration, the corresponding environment
variables will be set in the container being built.
output_stream (writer): stream to use for build logs
Returns:
A generator for the build output.
"""
Expand Down Expand Up @@ -1921,33 +1939,18 @@ def build(self, path, tag=None, quiet=False, fileobj=None,

args = command_builder.build([path])

magic_word = "Successfully built "
appear = False
with subprocess.Popen(args, stdout=subprocess.PIPE,
with subprocess.Popen(args, stdout=output_stream, stderr=sys.stderr,
universal_newlines=True) as p:
while True:
line = p.stdout.readline()
if not line:
break
if line.startswith(magic_word):
appear = True
yield json.dumps({"stream": line})

p.communicate()
if p.returncode != 0:
raise StreamOutputError()
raise BuildError(service, "Build failed")

with open(iidfile) as f:
line = f.readline()
image_id = line.split(":")[1].strip()
os.remove(iidfile)

# In case of `DOCKER_BUILDKIT=1`
# there is no success message already present in the output.
# Since that's the way `Service::build` gets the `image_id`
# it has to be added `manually`
if not appear:
yield json.dumps({"stream": "{}{}\n".format(magic_word, image_id)})
return image_id


class _CommandBuilder:
Expand Down
4 changes: 3 additions & 1 deletion tests/unit/service_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ def test_get_container_create_options_does_not_mutate_options(self):
assert service.options['environment'] == environment

assert opts['labels'][LABEL_CONFIG_HASH] == \
'689149e6041a85f6fb4945a2146a497ed43c8a5cbd8991753d875b165f1b4de4'
'6da0f3ec0d5adf901de304bdc7e0ee44ec5dd7adb08aebc20fe0dd791d4ee5a8'
assert opts['environment'] == ['also=real']

def test_get_container_create_options_sets_affinity_with_binds(self):
Expand Down Expand Up @@ -700,6 +700,7 @@ def test_config_dict(self):
config_dict = service.config_dict()
expected = {
'image_id': 'abcd',
'ipc_mode': None,
'options': {'image': 'example.com/foo'},
'links': [('one', 'one')],
'net': 'other',
Expand All @@ -723,6 +724,7 @@ def test_config_dict_with_network_mode_from_container(self):
config_dict = service.config_dict()
expected = {
'image_id': 'abcd',
'ipc_mode': None,
'options': {'image': 'example.com/foo'},
'links': [],
'networks': {},
Expand Down

0 comments on commit 575d676

Please sign in to comment.