Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[docker] fix image name when using sha256 for specs #3326

Merged
merged 2 commits into from
May 3, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions tests/core/test_dockerutil.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# stdlib
import unittest

# 3rd party
import mock

# project
from utils.dockerutil import DockerUtil

Expand Down Expand Up @@ -28,3 +31,31 @@ def test_parse_subsystem(self):
du = DockerUtil()
for line, exp_res in lines:
self.assertEquals(du._parse_subsystem(line), exp_res)

def test_image_name_from_container(self):
co = {'Image': 'redis:3.2'}
self.assertEqual('redis:3.2', DockerUtil().image_name_extractor(co))
pass

@mock.patch('docker.Client.inspect_image')
@mock.patch('docker.Client.__init__')
def test_image_name_from_image_repotags(self, mock_init, mock_image):
mock_image.return_value = {'RepoTags': ["redis:3.2"], 'RepoDigests': []}
mock_init.return_value = None
sha = 'sha256:e48e77eee11b6d9ac9fc35a23992b4158355a8ec3fd3725526eba3f467e4b6c9'
co = {'Image': sha}
self.assertEqual('redis:3.2', DockerUtil().image_name_extractor(co))
mock_image.assert_called_once_with(sha)

# Make sure cache is used insead of call again inspect_image
DockerUtil().image_name_extractor(co)
mock_image.assert_called_once()

@mock.patch('docker.Client.inspect_image')
@mock.patch('docker.Client.__init__')
def test_image_name_from_image_repodigests(self, mock_init, mock_image):
mock_image.return_value = {'RepoTags': [],
'RepoDigests': ['alpine@sha256:4f2d8bbad359e3e6f23c0498e009aaa3e2f31996cbea7269b78f92ee43647811']}
mock_init.return_value = None
co = {'Image': 'sha256:e48e77eee11b6d9ac9fc35a23992b4158355a8ec3fd3725526eba3f467e4b6d9'}
self.assertEqual('alpine', DockerUtil().image_name_extractor(co))
50 changes: 45 additions & 5 deletions utils/dockerutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ def __init__(self, **kwargs):
# At first run we'll just collect the events from the latest 60 secs
self._latest_event_collection_ts = int(time.time()) - 60

# Memory cache for sha256 to image name mapping
self._image_sha_to_name_mapping = {}

# Try to detect if we are on Swarm
self.fetch_swarm_state()

Expand Down Expand Up @@ -428,18 +431,19 @@ def find_cgroup_filename_pattern(cls, mountpoints, container_id):

raise MountException("Cannot find Docker cgroup directory. Be sure your system is supported.")

@classmethod
def image_tag_extractor(cls, entity, key):
if "Image" in entity:
split = entity["Image"].split(":")
def image_tag_extractor(self, entity, key):
name = self.image_name_extractor(entity)
if len(name):
split = name.split(":")
if len(split) <= key:
return None
elif len(split) > 2:
# if the repo is in the image name and has the form 'docker.clearbit:5000'
# the split will be like [repo_url, repo_port/image_name, image_tag]. Let's avoid that
split = [':'.join(split[:-1]), split[-1]]
return [split[key]]
if entity.get('RepoTags'):
# Entity is an image. TODO: deprecate?
elif entity.get('RepoTags'):
splits = [el.split(":") for el in entity["RepoTags"]]
tags = set()
for split in splits:
Expand All @@ -459,6 +463,42 @@ def image_tag_extractor(cls, entity, key):

return None

def image_name_extractor(self, co):
"""
Returns the image name for a container, either directly from the
container's Image property or by inspecting the image entity if
the reference is its sha256 sum and not its name.
Result is cached for performance, no invalidation planned as image
churn is low on typical hosts.
"""
if "Image" in co:
image = co.get('Image', '')
if image.startswith('sha256:'):
# Some orchestrators setup containers with image checksum instead of image name
try:
if image in self._image_sha_to_name_mapping:
return self._image_sha_to_name_mapping[image]
else:
image_spec = self.client.inspect_image(image)
try:
name = image_spec['RepoTags'][0]
self._image_sha_to_name_mapping[image] = name
return name
except (LookupError, TypeError) as e:
log.debug("Failed finding image name in RepoTag, trying RepoDigests: %s", e)
try:
name = image_spec['RepoDigests'][0]
name = name.split('@')[0] # Last resort, we get the name with no tag
self._image_sha_to_name_mapping[image] = name
return name
except (LookupError, TypeError) as e:
log.warning("Failed finding image name in RepoTag and RepoDigests: %s", e)
except Exception:
log.exception("Exception getting docker image name")
else:
return image
return None

@classmethod
def container_name_extractor(cls, co):
names = co.get('Names', [])
Expand Down
5 changes: 3 additions & 2 deletions utils/service_discovery/sd_docker_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ def __init__(self, agentConfig):
agentConfig['sd_config_backend'] = None
self.config_store = get_config_store(agentConfig=agentConfig)

self.docker_client = DockerUtil(config_store=self.config_store).client
self.dockerutil = DockerUtil(config_store=self.config_store)
self.docker_client = self.dockerutil.client
if Platform.is_k8s():
try:
self.kubeutil = KubeUtil()
Expand Down Expand Up @@ -347,7 +348,7 @@ def get_configs(self):
configs = {}
state = self._make_fetch_state()
containers = [(
container.get('Image'),
self.dockerutil.image_name_extractor(container),
container.get('Id'), container.get('Labels')
) for container in self.docker_client.containers()]

Expand Down