Skip to content

Commit bedabbf

Browse files
committed
Add methods for /distribution/<name>/json endpoint
Signed-off-by: Joffrey F <[email protected]>
1 parent f39c0dc commit bedabbf

File tree

4 files changed

+155
-1
lines changed

4 files changed

+155
-1
lines changed

docker/api/image.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,27 @@ def inspect_image(self, image):
245245
self._get(self._url("/images/{0}/json", image)), True
246246
)
247247

248+
@utils.minimum_version('1.30')
249+
@utils.check_resource('image')
250+
def inspect_distribution(self, image):
251+
"""
252+
Get image digest and platform information by contacting the registry.
253+
254+
Args:
255+
image (str): The image name to inspect
256+
257+
Returns:
258+
(dict): A dict containing distribution data
259+
260+
Raises:
261+
:py:class:`docker.errors.APIError`
262+
If the server returns an error.
263+
"""
264+
265+
return self._result(
266+
self._get(self._url("/distribution/{0}/json", image)), True
267+
)
268+
248269
def load_image(self, data, quiet=None):
249270
"""
250271
Load an image that was previously saved using

docker/models/images.py

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from ..api import APIClient
77
from ..constants import DEFAULT_DATA_CHUNK_SIZE
8-
from ..errors import BuildError, ImageLoadError
8+
from ..errors import BuildError, ImageLoadError, InvalidArgument
99
from ..utils import parse_repository_tag
1010
from ..utils.json_stream import json_stream
1111
from .resource import Collection, Model
@@ -105,6 +105,81 @@ def tag(self, repository, tag=None, **kwargs):
105105
return self.client.api.tag(self.id, repository, tag=tag, **kwargs)
106106

107107

108+
class RegistryData(Model):
109+
"""
110+
Image metadata stored on the registry, including available platforms.
111+
"""
112+
def __init__(self, image_name, *args, **kwargs):
113+
super(RegistryData, self).__init__(*args, **kwargs)
114+
self.image_name = image_name
115+
116+
@property
117+
def id(self):
118+
"""
119+
The ID of the object.
120+
"""
121+
return self.attrs['Descriptor']['digest']
122+
123+
@property
124+
def short_id(self):
125+
"""
126+
The ID of the image truncated to 10 characters, plus the ``sha256:``
127+
prefix.
128+
"""
129+
return self.id[:17]
130+
131+
def pull(self, platform=None):
132+
"""
133+
Pull the image digest.
134+
135+
Args:
136+
platform (str): The platform to pull the image for.
137+
Default: ``None``
138+
139+
Returns:
140+
(:py:class:`Image`): A reference to the pulled image.
141+
"""
142+
repository, _ = parse_repository_tag(self.image_name)
143+
return self.collection.pull(repository, tag=self.id, platform=platform)
144+
145+
def has_platform(self, platform):
146+
"""
147+
Check whether the given platform identifier is available for this
148+
digest.
149+
150+
Args:
151+
platform (str or dict): A string using the ``os[/arch[/variant]]``
152+
format, or a platform dictionary.
153+
154+
Returns:
155+
(bool): ``True`` if the platform is recognized as available,
156+
``False`` otherwise.
157+
158+
Raises:
159+
:py:class:`docker.errors.InvalidArgument`
160+
If the platform argument is not a valid descriptor.
161+
"""
162+
if platform and not isinstance(platform, dict):
163+
parts = platform.split('/')
164+
if len(parts) > 3 or len(parts) < 1:
165+
raise InvalidArgument(
166+
'"{0}" is not a valid platform descriptor'.format(platform)
167+
)
168+
platform = {'os': parts[0]}
169+
if len(parts) > 2:
170+
platform['variant'] = parts[2]
171+
if len(parts) > 1:
172+
platform['architecture'] = parts[1]
173+
return normalize_platform(
174+
platform, self.client.version()
175+
) in self.attrs['Platforms']
176+
177+
def reload(self):
178+
self.attrs = self.client.api.inspect_distribution(self.image_name)
179+
180+
reload.__doc__ = Model.reload.__doc__
181+
182+
108183
class ImageCollection(Collection):
109184
model = Image
110185

@@ -219,6 +294,26 @@ def get(self, name):
219294
"""
220295
return self.prepare_model(self.client.api.inspect_image(name))
221296

297+
def get_registry_data(self, name):
298+
"""
299+
Gets the registry data for an image.
300+
301+
Args:
302+
name (str): The name of the image.
303+
304+
Returns:
305+
(:py:class:`RegistryData`): The data object.
306+
Raises:
307+
:py:class:`docker.errors.APIError`
308+
If the server returns an error.
309+
"""
310+
return RegistryData(
311+
image_name=name,
312+
attrs=self.client.api.inspect_distribution(name),
313+
client=self.client,
314+
collection=self,
315+
)
316+
222317
def list(self, name=None, all=False, filters=None):
223318
"""
224319
List images on the server.
@@ -336,3 +431,13 @@ def search(self, *args, **kwargs):
336431
def prune(self, filters=None):
337432
return self.client.api.prune_images(filters=filters)
338433
prune.__doc__ = APIClient.prune_images.__doc__
434+
435+
436+
def normalize_platform(platform, engine_info):
437+
if platform is None:
438+
platform = {}
439+
if 'os' not in platform:
440+
platform['os'] = engine_info['Os']
441+
if 'architecture' not in platform:
442+
platform['architecture'] = engine_info['Arch']
443+
return platform

docs/images.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Methods available on ``client.images``:
1212
1313
.. automethod:: build
1414
.. automethod:: get
15+
.. automethod:: get_registry_data
1516
.. automethod:: list(**kwargs)
1617
.. automethod:: load
1718
.. automethod:: prune
@@ -41,3 +42,21 @@ Image objects
4142
.. automethod:: reload
4243
.. automethod:: save
4344
.. automethod:: tag
45+
46+
RegistryData objects
47+
--------------------
48+
49+
.. autoclass:: RegistryData()
50+
51+
.. py:attribute:: attrs
52+
53+
The raw representation of this object from the server.
54+
55+
.. autoattribute:: id
56+
.. autoattribute:: short_id
57+
58+
59+
60+
.. automethod:: has_platform
61+
.. automethod:: pull
62+
.. automethod:: reload

tests/integration/api_image_test.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,3 +357,12 @@ def test_get_image_load_image(self):
357357
success = True
358358
break
359359
assert success is True
360+
361+
362+
@requires_api_version('1.30')
363+
class InspectDistributionTest(BaseAPIIntegrationTest):
364+
def test_inspect_distribution(self):
365+
data = self.client.inspect_distribution('busybox:latest')
366+
assert data is not None
367+
assert 'Platforms' in data
368+
assert {'os': 'linux', 'architecture': 'amd64'} in data['Platforms']

0 commit comments

Comments
 (0)