summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoffrey F <joffrey@docker.com>2018-03-26 19:01:50 -0700
committerJoffrey F <joffrey@docker.com>2018-03-26 19:08:55 -0700
commite21174721f47e1cb6d591983daf85c3749fe5f4a (patch)
treec279a72bb642d89b03d8661b0575429c9d99eb30
parent12a6833eba4f64be1386d3da0d605156319c5946 (diff)
downloaddocker-py-distribution_inspect.tar.gz
Add methods for /distribution/<name>/json endpointdistribution_inspect
Signed-off-by: Joffrey F <joffrey@docker.com>
-rw-r--r--docker/api/image.py21
-rw-r--r--docker/models/images.py107
-rw-r--r--docs/images.rst19
-rw-r--r--tests/integration/api_image_test.py9
4 files changed, 155 insertions, 1 deletions
diff --git a/docker/api/image.py b/docker/api/image.py
index 3ebca32..5f05d88 100644
--- a/docker/api/image.py
+++ b/docker/api/image.py
@@ -245,6 +245,27 @@ class ImageApiMixin(object):
self._get(self._url("/images/{0}/json", image)), True
)
+ @utils.minimum_version('1.30')
+ @utils.check_resource('image')
+ def inspect_distribution(self, image):
+ """
+ Get image digest and platform information by contacting the registry.
+
+ Args:
+ image (str): The image name to inspect
+
+ Returns:
+ (dict): A dict containing distribution data
+
+ Raises:
+ :py:class:`docker.errors.APIError`
+ If the server returns an error.
+ """
+
+ return self._result(
+ self._get(self._url("/distribution/{0}/json", image)), True
+ )
+
def load_image(self, data, quiet=None):
"""
Load an image that was previously saved using
diff --git a/docker/models/images.py b/docker/models/images.py
index bb24eb5..d4893bb 100644
--- a/docker/models/images.py
+++ b/docker/models/images.py
@@ -5,7 +5,7 @@ import six
from ..api import APIClient
from ..constants import DEFAULT_DATA_CHUNK_SIZE
-from ..errors import BuildError, ImageLoadError
+from ..errors import BuildError, ImageLoadError, InvalidArgument
from ..utils import parse_repository_tag
from ..utils.json_stream import json_stream
from .resource import Collection, Model
@@ -105,6 +105,81 @@ class Image(Model):
return self.client.api.tag(self.id, repository, tag=tag, **kwargs)
+class RegistryData(Model):
+ """
+ Image metadata stored on the registry, including available platforms.
+ """
+ def __init__(self, image_name, *args, **kwargs):
+ super(RegistryData, self).__init__(*args, **kwargs)
+ self.image_name = image_name
+
+ @property
+ def id(self):
+ """
+ The ID of the object.
+ """
+ return self.attrs['Descriptor']['digest']
+
+ @property
+ def short_id(self):
+ """
+ The ID of the image truncated to 10 characters, plus the ``sha256:``
+ prefix.
+ """
+ return self.id[:17]
+
+ def pull(self, platform=None):
+ """
+ Pull the image digest.
+
+ Args:
+ platform (str): The platform to pull the image for.
+ Default: ``None``
+
+ Returns:
+ (:py:class:`Image`): A reference to the pulled image.
+ """
+ repository, _ = parse_repository_tag(self.image_name)
+ return self.collection.pull(repository, tag=self.id, platform=platform)
+
+ def has_platform(self, platform):
+ """
+ Check whether the given platform identifier is available for this
+ digest.
+
+ Args:
+ platform (str or dict): A string using the ``os[/arch[/variant]]``
+ format, or a platform dictionary.
+
+ Returns:
+ (bool): ``True`` if the platform is recognized as available,
+ ``False`` otherwise.
+
+ Raises:
+ :py:class:`docker.errors.InvalidArgument`
+ If the platform argument is not a valid descriptor.
+ """
+ if platform and not isinstance(platform, dict):
+ parts = platform.split('/')
+ if len(parts) > 3 or len(parts) < 1:
+ raise InvalidArgument(
+ '"{0}" is not a valid platform descriptor'.format(platform)
+ )
+ platform = {'os': parts[0]}
+ if len(parts) > 2:
+ platform['variant'] = parts[2]
+ if len(parts) > 1:
+ platform['architecture'] = parts[1]
+ return normalize_platform(
+ platform, self.client.version()
+ ) in self.attrs['Platforms']
+
+ def reload(self):
+ self.attrs = self.client.api.inspect_distribution(self.image_name)
+
+ reload.__doc__ = Model.reload.__doc__
+
+
class ImageCollection(Collection):
model = Image
@@ -219,6 +294,26 @@ class ImageCollection(Collection):
"""
return self.prepare_model(self.client.api.inspect_image(name))
+ def get_registry_data(self, name):
+ """
+ Gets the registry data for an image.
+
+ Args:
+ name (str): The name of the image.
+
+ Returns:
+ (:py:class:`RegistryData`): The data object.
+ Raises:
+ :py:class:`docker.errors.APIError`
+ If the server returns an error.
+ """
+ return RegistryData(
+ image_name=name,
+ attrs=self.client.api.inspect_distribution(name),
+ client=self.client,
+ collection=self,
+ )
+
def list(self, name=None, all=False, filters=None):
"""
List images on the server.
@@ -336,3 +431,13 @@ class ImageCollection(Collection):
def prune(self, filters=None):
return self.client.api.prune_images(filters=filters)
prune.__doc__ = APIClient.prune_images.__doc__
+
+
+def normalize_platform(platform, engine_info):
+ if platform is None:
+ platform = {}
+ if 'os' not in platform:
+ platform['os'] = engine_info['Os']
+ if 'architecture' not in platform:
+ platform['architecture'] = engine_info['Arch']
+ return platform
diff --git a/docs/images.rst b/docs/images.rst
index 12b0fd1..4d425e9 100644
--- a/docs/images.rst
+++ b/docs/images.rst
@@ -12,6 +12,7 @@ Methods available on ``client.images``:
.. automethod:: build
.. automethod:: get
+ .. automethod:: get_registry_data
.. automethod:: list(**kwargs)
.. automethod:: load
.. automethod:: prune
@@ -41,3 +42,21 @@ Image objects
.. automethod:: reload
.. automethod:: save
.. automethod:: tag
+
+RegistryData objects
+--------------------
+
+.. autoclass:: RegistryData()
+
+ .. py:attribute:: attrs
+
+ The raw representation of this object from the server.
+
+ .. autoattribute:: id
+ .. autoattribute:: short_id
+
+
+
+ .. automethod:: has_platform
+ .. automethod:: pull
+ .. automethod:: reload
diff --git a/tests/integration/api_image_test.py b/tests/integration/api_image_test.py
index ab638c9..050e7f3 100644
--- a/tests/integration/api_image_test.py
+++ b/tests/integration/api_image_test.py
@@ -357,3 +357,12 @@ class SaveLoadImagesTest(BaseAPIIntegrationTest):
success = True
break
assert success is True
+
+
+@requires_api_version('1.30')
+class InspectDistributionTest(BaseAPIIntegrationTest):
+ def test_inspect_distribution(self):
+ data = self.client.inspect_distribution('busybox:latest')
+ assert data is not None
+ assert 'Platforms' in data
+ assert {'os': 'linux', 'architecture': 'amd64'} in data['Platforms']