Skip to content

Commit

Permalink
fix docker image name getting when using sha256 for specs
Browse files Browse the repository at this point in the history
Some orchestrators init containers with the image sha256 instead of the
image name, and docker inspect returns this. If it's the case, make
a docker image inspect call to get the image name and cache the result
  • Loading branch information
xvello committed May 2, 2017
1 parent 58d529f commit e0655cf
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 7 deletions.
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.get('RepoTags')[0]
self._image_sha_to_name_mapping[image] = name
return name
except Exception:
pass
try:
name = image_spec.get('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 Exception:
pass
except Exception:
pass
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

0 comments on commit e0655cf

Please sign in to comment.