Resolve digests without pulling image

If there is no image locally `docker-compose --resolve-image-digests`
will try and get the digest from the repository.

Fixes https://github.com/docker/compose/issues/5818

Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
This commit is contained in:
Djordje Lukic 2019-01-11 10:04:57 +01:00
parent cf96fcb4af
commit 0c20fc5d91
3 changed files with 46 additions and 12 deletions

View File

@ -95,19 +95,10 @@ def get_image_digest(service, allow_push=False):
if separator == '@':
return service.options['image']
try:
image = service.image()
except NoSuchImageError:
action = 'build' if 'build' in service.options else 'pull'
raise UserError(
"Image not found for service '{service}'. "
"You might need to run `docker-compose {action} {service}`."
.format(service=service.name, action=action))
digest = get_digest(service)
if image['RepoDigests']:
# TODO: pick a digest based on the image tag if there are multiple
# digests
return image['RepoDigests'][0]
if digest:
return digest
if 'build' not in service.options:
raise NeedsPull(service.image_name, service.name)
@ -118,6 +109,32 @@ def get_image_digest(service, allow_push=False):
return push_image(service)
def get_digest(service):
digest = None
try:
image = service.image()
# TODO: pick a digest based on the image tag if there are multiple
# digests
if image['RepoDigests']:
digest = image['RepoDigests'][0]
except NoSuchImageError:
try:
# Fetch the image digest from the registry
distribution = service.get_image_registry_data()
if distribution['Descriptor']['digest']:
digest = '{image_name}@{digest}'.format(
image_name=service.image_name,
digest=distribution['Descriptor']['digest']
)
except NoSuchImageError:
raise UserError(
"Digest not found for service '{service}'. "
"Repository does not exist or may require 'docker login'"
.format(service=service.name))
return digest
def push_image(service):
try:
digest = service.push()

View File

@ -363,6 +363,12 @@ class Service(object):
"rebuild this image you must use `docker-compose build` or "
"`docker-compose up --build`.".format(self.name))
def get_image_registry_data(self):
try:
return self.client.inspect_distribution(self.image_name)
except APIError:
raise NoSuchImageError("Image '{}' not found".format(self.image_name))
def image(self):
try:
return self.client.inspect_image(self.image_name)

View File

@ -10,6 +10,7 @@ from compose import service
from compose.cli.errors import UserError
from compose.config.config import Config
from compose.const import COMPOSEFILE_V2_0 as V2_0
from compose.service import NoSuchImageError
@pytest.fixture
@ -35,6 +36,16 @@ def test_get_image_digest_image_uses_digest(mock_service):
assert not mock_service.image.called
def test_get_image_digest_from_repository(mock_service):
mock_service.options['image'] = 'abcd'
mock_service.image_name = 'abcd'
mock_service.image.side_effect = NoSuchImageError(None)
mock_service.get_image_registry_data.return_value = {'Descriptor': {'digest': 'digest'}}
digest = bundle.get_image_digest(mock_service)
assert digest == 'abcd@digest'
def test_get_image_digest_no_image(mock_service):
with pytest.raises(UserError) as exc:
bundle.get_image_digest(service.Service(name='theservice'))