Merge pull request #7052 from ndeloof/pull_can_build

Report image we can't pull and must be built
This commit is contained in:
Ulysses Souza 2019-11-29 15:48:38 +01:00 committed by GitHub
commit e82d38f333
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 77 additions and 28 deletions

View File

@ -114,3 +114,13 @@ def get_digest_from_push(events):
if digest: if digest:
return digest return digest
return None return None
def read_status(event):
status = event['status'].lower()
if 'progressDetail' in event:
detail = event['progressDetail']
if 'current' in detail and 'total' in detail:
percentage = float(detail['current']) / float(detail['total'])
status = '{} ({:.1%})'.format(status, percentage)
return status

View File

@ -11,6 +11,8 @@ from os import path
import enum import enum
import six import six
from docker.errors import APIError from docker.errors import APIError
from docker.errors import ImageNotFound
from docker.errors import NotFound
from docker.utils import version_lt from docker.utils import version_lt
from . import parallel from . import parallel
@ -25,6 +27,7 @@ from .container import Container
from .network import build_networks from .network import build_networks
from .network import get_networks from .network import get_networks
from .network import ProjectNetworks from .network import ProjectNetworks
from .progress_stream import read_status
from .service import BuildAction from .service import BuildAction
from .service import ContainerNetworkMode from .service import ContainerNetworkMode
from .service import ContainerPidMode from .service import ContainerPidMode
@ -619,29 +622,50 @@ class Project(object):
def pull(self, service_names=None, ignore_pull_failures=False, parallel_pull=False, silent=False, def pull(self, service_names=None, ignore_pull_failures=False, parallel_pull=False, silent=False,
include_deps=False): include_deps=False):
services = self.get_services(service_names, include_deps) services = self.get_services(service_names, include_deps)
msg = not silent and 'Pulling' or None
if parallel_pull: if parallel_pull:
self.parallel_pull(services, silent=silent)
else:
must_build = []
for service in services:
try:
service.pull(ignore_pull_failures, silent=silent)
except (ImageNotFound, NotFound):
if service.can_be_built():
must_build.append(service.name)
else:
raise
if len(must_build):
log.warning('Some service image(s) must be built from source by running:\n'
' docker-compose build {}'
.format(' '.join(must_build)))
def parallel_pull(self, services, ignore_pull_failures=False, silent=False):
msg = 'Pulling' if not silent else None
must_build = []
def pull_service(service): def pull_service(service):
strm = service.pull(ignore_pull_failures, True, stream=True) strm = service.pull(ignore_pull_failures, True, stream=True)
if strm is None: # Attempting to pull service with no `image` key is a no-op if strm is None: # Attempting to pull service with no `image` key is a no-op
return return
try:
writer = parallel.get_stream_writer() writer = parallel.get_stream_writer()
for event in strm: for event in strm:
if 'status' not in event: if 'status' not in event:
continue continue
status = event['status'].lower() status = read_status(event)
if 'progressDetail' in event:
detail = event['progressDetail']
if 'current' in detail and 'total' in detail:
percentage = float(detail['current']) / float(detail['total'])
status = '{} ({:.1%})'.format(status, percentage)
writer.write( writer.write(
msg, service.name, truncate_string(status), lambda s: s msg, service.name, truncate_string(status), lambda s: s
) )
except (ImageNotFound, NotFound):
if service.can_be_built():
must_build.append(service.name)
else:
raise
_, errors = parallel.parallel_execute( _, errors = parallel.parallel_execute(
services, services,
@ -650,16 +674,17 @@ class Project(object):
msg, msg,
limit=5, limit=5,
) )
if len(must_build):
log.warning('Some service image(s) must be built from source by running:\n'
' docker-compose build {}'
.format(' '.join(must_build)))
if len(errors): if len(errors):
combined_errors = '\n'.join([ combined_errors = '\n'.join([
e.decode('utf-8') if isinstance(e, six.binary_type) else e for e in errors.values() e.decode('utf-8') if isinstance(e, six.binary_type) else e for e in errors.values()
]) ])
raise ProjectError(combined_errors) raise ProjectError(combined_errors)
else:
for service in services:
service.pull(ignore_pull_failures, silent=silent)
def push(self, service_names=None, ignore_push_failures=False): def push(self, service_names=None, ignore_push_failures=False):
unique_images = set() unique_images = set()
for service in self.get_services(service_names, include_deps=False): for service in self.get_services(service_names, include_deps=False):

View File

@ -694,6 +694,14 @@ services:
result.stderr result.stderr
) )
def test_pull_can_build(self):
result = self.dispatch([
'-f', 'can-build-pull-failures.yml', 'pull'],
returncode=0
)
assert 'Some service image(s) must be built from source' in result.stderr
assert 'docker-compose build can_build' in result.stderr
def test_pull_with_no_deps(self): def test_pull_with_no_deps(self):
self.base_dir = 'tests/fixtures/links-composefile' self.base_dir = 'tests/fixtures/links-composefile'
result = self.dispatch(['pull', '--no-parallel', 'web']) result = self.dispatch(['pull', '--no-parallel', 'web'])

View File

@ -0,0 +1,6 @@
version: '3'
services:
can_build:
image: nonexisting-image-but-can-build:latest
build: .
command: top