mirror of https://github.com/docker/compose.git
Merge pull request #1889 from mrfuxi/1670-images-digest
Allow to specify image by digest. Fixes #1670
This commit is contained in:
commit
340eaf44fa
|
@ -757,9 +757,9 @@ class Service(object):
|
||||||
if 'image' not in self.options:
|
if 'image' not in self.options:
|
||||||
return
|
return
|
||||||
|
|
||||||
repo, tag = parse_repository_tag(self.options['image'])
|
repo, tag, separator = parse_repository_tag(self.options['image'])
|
||||||
tag = tag or 'latest'
|
tag = tag or 'latest'
|
||||||
log.info('Pulling %s (%s:%s)...' % (self.name, repo, tag))
|
log.info('Pulling %s (%s%s%s)...' % (self.name, repo, separator, tag))
|
||||||
output = self.client.pull(
|
output = self.client.pull(
|
||||||
repo,
|
repo,
|
||||||
tag=tag,
|
tag=tag,
|
||||||
|
@ -780,14 +780,31 @@ def build_container_name(project, service, number, one_off=False):
|
||||||
|
|
||||||
# Images
|
# Images
|
||||||
|
|
||||||
|
def parse_repository_tag(repo_path):
|
||||||
|
"""Splits image identification into base image path, tag/digest
|
||||||
|
and it's separator.
|
||||||
|
|
||||||
def parse_repository_tag(s):
|
Example:
|
||||||
if ":" not in s:
|
|
||||||
return s, ""
|
>>> parse_repository_tag('user/repo@sha256:digest')
|
||||||
repo, tag = s.rsplit(":", 1)
|
('user/repo', 'sha256:digest', '@')
|
||||||
if "/" in tag:
|
>>> parse_repository_tag('user/repo:v1')
|
||||||
return s, ""
|
('user/repo', 'v1', ':')
|
||||||
return repo, tag
|
"""
|
||||||
|
tag_separator = ":"
|
||||||
|
digest_separator = "@"
|
||||||
|
|
||||||
|
if digest_separator in repo_path:
|
||||||
|
repo, tag = repo_path.rsplit(digest_separator, 1)
|
||||||
|
return repo, tag, digest_separator
|
||||||
|
|
||||||
|
repo, tag = repo_path, ""
|
||||||
|
if tag_separator in repo_path:
|
||||||
|
repo, tag = repo_path.rsplit(tag_separator, 1)
|
||||||
|
if "/" in tag:
|
||||||
|
repo, tag = repo_path, ""
|
||||||
|
|
||||||
|
return repo, tag, tag_separator
|
||||||
|
|
||||||
|
|
||||||
# Volumes
|
# Volumes
|
||||||
|
|
|
@ -25,12 +25,13 @@ Values for configuration options can contain environment variables, e.g.
|
||||||
|
|
||||||
### image
|
### image
|
||||||
|
|
||||||
Tag or partial image ID. Can be local or remote - Compose will attempt to
|
Tag, partial image ID or digest. Can be local or remote - Compose will attempt to
|
||||||
pull if it doesn't exist locally.
|
pull if it doesn't exist locally.
|
||||||
|
|
||||||
image: ubuntu
|
image: ubuntu
|
||||||
image: orchardup/postgresql
|
image: orchardup/postgresql
|
||||||
image: a4bc65fd
|
image: a4bc65fd
|
||||||
|
image: busybox@sha256:38a203e1986cf79639cfb9b2e1d6e773de84002feea2d4eb006b52004ee8502d
|
||||||
|
|
||||||
### build
|
### build
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
simple:
|
||||||
|
image: busybox:latest
|
||||||
|
command: top
|
||||||
|
digest:
|
||||||
|
image: busybox@sha256:38a203e1986cf79639cfb9b2e1d6e773de84002feea2d4eb006b52004ee8502d
|
||||||
|
command: top
|
|
@ -88,6 +88,12 @@ class CLITestCase(DockerClientTestCase):
|
||||||
mock_logging.info.assert_any_call('Pulling simple (busybox:latest)...')
|
mock_logging.info.assert_any_call('Pulling simple (busybox:latest)...')
|
||||||
mock_logging.info.assert_any_call('Pulling another (busybox:latest)...')
|
mock_logging.info.assert_any_call('Pulling another (busybox:latest)...')
|
||||||
|
|
||||||
|
@patch('compose.service.log')
|
||||||
|
def test_pull_with_digest(self, mock_logging):
|
||||||
|
self.command.dispatch(['-f', 'digest.yml', 'pull'], None)
|
||||||
|
mock_logging.info.assert_any_call('Pulling simple (busybox:latest)...')
|
||||||
|
mock_logging.info.assert_any_call('Pulling digest (busybox@sha256:38a203e1986cf79639cfb9b2e1d6e773de84002feea2d4eb006b52004ee8502d)...')
|
||||||
|
|
||||||
@patch('sys.stdout', new_callable=StringIO)
|
@patch('sys.stdout', new_callable=StringIO)
|
||||||
def test_build_no_cache(self, mock_stdout):
|
def test_build_no_cache(self, mock_stdout):
|
||||||
self.command.base_dir = 'tests/fixtures/simple-dockerfile'
|
self.command.base_dir = 'tests/fixtures/simple-dockerfile'
|
||||||
|
|
|
@ -192,6 +192,16 @@ class ServiceTest(unittest.TestCase):
|
||||||
tag='latest',
|
tag='latest',
|
||||||
stream=True)
|
stream=True)
|
||||||
|
|
||||||
|
@mock.patch('compose.service.log', autospec=True)
|
||||||
|
def test_pull_image_digest(self, mock_log):
|
||||||
|
service = Service('foo', client=self.mock_client, image='someimage@sha256:1234')
|
||||||
|
service.pull()
|
||||||
|
self.mock_client.pull.assert_called_once_with(
|
||||||
|
'someimage',
|
||||||
|
tag='sha256:1234',
|
||||||
|
stream=True)
|
||||||
|
mock_log.info.assert_called_once_with('Pulling foo (someimage@sha256:1234)...')
|
||||||
|
|
||||||
@mock.patch('compose.service.Container', autospec=True)
|
@mock.patch('compose.service.Container', autospec=True)
|
||||||
def test_recreate_container(self, _):
|
def test_recreate_container(self, _):
|
||||||
mock_container = mock.create_autospec(Container)
|
mock_container = mock.create_autospec(Container)
|
||||||
|
@ -217,12 +227,16 @@ class ServiceTest(unittest.TestCase):
|
||||||
mock_container.stop.assert_called_once_with(timeout=1)
|
mock_container.stop.assert_called_once_with(timeout=1)
|
||||||
|
|
||||||
def test_parse_repository_tag(self):
|
def test_parse_repository_tag(self):
|
||||||
self.assertEqual(parse_repository_tag("root"), ("root", ""))
|
self.assertEqual(parse_repository_tag("root"), ("root", "", ":"))
|
||||||
self.assertEqual(parse_repository_tag("root:tag"), ("root", "tag"))
|
self.assertEqual(parse_repository_tag("root:tag"), ("root", "tag", ":"))
|
||||||
self.assertEqual(parse_repository_tag("user/repo"), ("user/repo", ""))
|
self.assertEqual(parse_repository_tag("user/repo"), ("user/repo", "", ":"))
|
||||||
self.assertEqual(parse_repository_tag("user/repo:tag"), ("user/repo", "tag"))
|
self.assertEqual(parse_repository_tag("user/repo:tag"), ("user/repo", "tag", ":"))
|
||||||
self.assertEqual(parse_repository_tag("url:5000/repo"), ("url:5000/repo", ""))
|
self.assertEqual(parse_repository_tag("url:5000/repo"), ("url:5000/repo", "", ":"))
|
||||||
self.assertEqual(parse_repository_tag("url:5000/repo:tag"), ("url:5000/repo", "tag"))
|
self.assertEqual(parse_repository_tag("url:5000/repo:tag"), ("url:5000/repo", "tag", ":"))
|
||||||
|
|
||||||
|
self.assertEqual(parse_repository_tag("root@sha256:digest"), ("root", "sha256:digest", "@"))
|
||||||
|
self.assertEqual(parse_repository_tag("user/repo@sha256:digest"), ("user/repo", "sha256:digest", "@"))
|
||||||
|
self.assertEqual(parse_repository_tag("url:5000/repo@sha256:digest"), ("url:5000/repo", "sha256:digest", "@"))
|
||||||
|
|
||||||
@mock.patch('compose.service.Container', autospec=True)
|
@mock.patch('compose.service.Container', autospec=True)
|
||||||
def test_create_container_latest_is_used_when_no_tag_specified(self, mock_container):
|
def test_create_container_latest_is_used_when_no_tag_specified(self, mock_container):
|
||||||
|
|
Loading…
Reference in New Issue