Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

images.py 18KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. import itertools
  2. import re
  3. import warnings
  4. from ..api import APIClient
  5. from ..constants import DEFAULT_DATA_CHUNK_SIZE
  6. from ..errors import BuildError, ImageLoadError, InvalidArgument
  7. from ..utils import parse_repository_tag
  8. from ..utils.json_stream import json_stream
  9. from .resource import Collection, Model
  10. class Image(Model):
  11. """
  12. An image on the server.
  13. """
  14. def __repr__(self):
  15. return "<{}: '{}'>".format(
  16. self.__class__.__name__,
  17. "', '".join(self.tags),
  18. )
  19. @property
  20. def labels(self):
  21. """
  22. The labels of an image as dictionary.
  23. """
  24. result = self.attrs['Config'].get('Labels')
  25. return result or {}
  26. @property
  27. def short_id(self):
  28. """
  29. The ID of the image truncated to 12 characters, plus the ``sha256:``
  30. prefix.
  31. """
  32. if self.id.startswith('sha256:'):
  33. return self.id[:19]
  34. return self.id[:12]
  35. @property
  36. def tags(self):
  37. """
  38. The image's tags.
  39. """
  40. tags = self.attrs.get('RepoTags')
  41. if tags is None:
  42. tags = []
  43. return [tag for tag in tags if tag != '<none>:<none>']
  44. def history(self):
  45. """
  46. Show the history of an image.
  47. Returns:
  48. (str): The history of the image.
  49. Raises:
  50. :py:class:`docker.errors.APIError`
  51. If the server returns an error.
  52. """
  53. return self.client.api.history(self.id)
  54. def remove(self, force=False, noprune=False):
  55. """
  56. Remove this image.
  57. Args:
  58. force (bool): Force removal of the image
  59. noprune (bool): Do not delete untagged parents
  60. Raises:
  61. :py:class:`docker.errors.APIError`
  62. If the server returns an error.
  63. """
  64. return self.client.api.remove_image(
  65. self.id,
  66. force=force,
  67. noprune=noprune,
  68. )
  69. def save(self, chunk_size=DEFAULT_DATA_CHUNK_SIZE, named=False):
  70. """
  71. Get a tarball of an image. Similar to the ``docker save`` command.
  72. Args:
  73. chunk_size (int): The generator will return up to that much data
  74. per iteration, but may return less. If ``None``, data will be
  75. streamed as it is received. Default: 2 MB
  76. named (str or bool): If ``False`` (default), the tarball will not
  77. retain repository and tag information for this image. If set
  78. to ``True``, the first tag in the :py:attr:`~tags` list will
  79. be used to identify the image. Alternatively, any element of
  80. the :py:attr:`~tags` list can be used as an argument to use
  81. that specific tag as the saved identifier.
  82. Returns:
  83. (generator): A stream of raw archive data.
  84. Raises:
  85. :py:class:`docker.errors.APIError`
  86. If the server returns an error.
  87. Example:
  88. >>> image = cli.images.get("busybox:latest")
  89. >>> f = open('/tmp/busybox-latest.tar', 'wb')
  90. >>> for chunk in image.save():
  91. >>> f.write(chunk)
  92. >>> f.close()
  93. """
  94. img = self.id
  95. if named:
  96. img = self.tags[0] if self.tags else img
  97. if isinstance(named, str):
  98. if named not in self.tags:
  99. raise InvalidArgument(
  100. f"{named} is not a valid tag for this image"
  101. )
  102. img = named
  103. return self.client.api.get_image(img, chunk_size)
  104. def tag(self, repository, tag=None, **kwargs):
  105. """
  106. Tag this image into a repository. Similar to the ``docker tag``
  107. command.
  108. Args:
  109. repository (str): The repository to set for the tag
  110. tag (str): The tag name
  111. force (bool): Force
  112. Raises:
  113. :py:class:`docker.errors.APIError`
  114. If the server returns an error.
  115. Returns:
  116. (bool): ``True`` if successful
  117. """
  118. return self.client.api.tag(self.id, repository, tag=tag, **kwargs)
  119. class RegistryData(Model):
  120. """
  121. Image metadata stored on the registry, including available platforms.
  122. """
  123. def __init__(self, image_name, *args, **kwargs):
  124. super().__init__(*args, **kwargs)
  125. self.image_name = image_name
  126. @property
  127. def id(self):
  128. """
  129. The ID of the object.
  130. """
  131. return self.attrs['Descriptor']['digest']
  132. @property
  133. def short_id(self):
  134. """
  135. The ID of the image truncated to 12 characters, plus the ``sha256:``
  136. prefix.
  137. """
  138. return self.id[:19]
  139. def pull(self, platform=None):
  140. """
  141. Pull the image digest.
  142. Args:
  143. platform (str): The platform to pull the image for.
  144. Default: ``None``
  145. Returns:
  146. (:py:class:`Image`): A reference to the pulled image.
  147. """
  148. repository, _ = parse_repository_tag(self.image_name)
  149. return self.collection.pull(repository, tag=self.id, platform=platform)
  150. def has_platform(self, platform):
  151. """
  152. Check whether the given platform identifier is available for this
  153. digest.
  154. Args:
  155. platform (str or dict): A string using the ``os[/arch[/variant]]``
  156. format, or a platform dictionary.
  157. Returns:
  158. (bool): ``True`` if the platform is recognized as available,
  159. ``False`` otherwise.
  160. Raises:
  161. :py:class:`docker.errors.InvalidArgument`
  162. If the platform argument is not a valid descriptor.
  163. """
  164. if platform and not isinstance(platform, dict):
  165. parts = platform.split('/')
  166. if len(parts) > 3 or len(parts) < 1:
  167. raise InvalidArgument(
  168. f'"{platform}" is not a valid platform descriptor'
  169. )
  170. platform = {'os': parts[0]}
  171. if len(parts) > 2:
  172. platform['variant'] = parts[2]
  173. if len(parts) > 1:
  174. platform['architecture'] = parts[1]
  175. return normalize_platform(
  176. platform, self.client.version()
  177. ) in self.attrs['Platforms']
  178. def reload(self):
  179. self.attrs = self.client.api.inspect_distribution(self.image_name)
  180. reload.__doc__ = Model.reload.__doc__
  181. class ImageCollection(Collection):
  182. model = Image
  183. def build(self, **kwargs):
  184. """
  185. Build an image and return it. Similar to the ``docker build``
  186. command. Either ``path`` or ``fileobj`` must be set.
  187. If you already have a tar file for the Docker build context (including
  188. a Dockerfile), pass a readable file-like object to ``fileobj``
  189. and also pass ``custom_context=True``. If the stream is also
  190. compressed, set ``encoding`` to the correct value (e.g ``gzip``).
  191. If you want to get the raw output of the build, use the
  192. :py:meth:`~docker.api.build.BuildApiMixin.build` method in the
  193. low-level API.
  194. Args:
  195. path (str): Path to the directory containing the Dockerfile
  196. fileobj: A file object to use as the Dockerfile. (Or a file-like
  197. object)
  198. tag (str): A tag to add to the final image
  199. quiet (bool): Whether to return the status
  200. nocache (bool): Don't use the cache when set to ``True``
  201. rm (bool): Remove intermediate containers. The ``docker build``
  202. command now defaults to ``--rm=true``, but we have kept the old
  203. default of `False` to preserve backward compatibility
  204. timeout (int): HTTP timeout
  205. custom_context (bool): Optional if using ``fileobj``
  206. encoding (str): The encoding for a stream. Set to ``gzip`` for
  207. compressing
  208. pull (bool): Downloads any updates to the FROM image in Dockerfiles
  209. forcerm (bool): Always remove intermediate containers, even after
  210. unsuccessful builds
  211. dockerfile (str): path within the build context to the Dockerfile
  212. buildargs (dict): A dictionary of build arguments
  213. container_limits (dict): A dictionary of limits applied to each
  214. container created by the build process. Valid keys:
  215. - memory (int): set memory limit for build
  216. - memswap (int): Total memory (memory + swap), -1 to disable
  217. swap
  218. - cpushares (int): CPU shares (relative weight)
  219. - cpusetcpus (str): CPUs in which to allow execution, e.g.,
  220. ``"0-3"``, ``"0,1"``
  221. shmsize (int): Size of `/dev/shm` in bytes. The size must be
  222. greater than 0. If omitted the system uses 64MB
  223. labels (dict): A dictionary of labels to set on the image
  224. cache_from (list): A list of images used for build cache
  225. resolution
  226. target (str): Name of the build-stage to build in a multi-stage
  227. Dockerfile
  228. network_mode (str): networking mode for the run commands during
  229. build
  230. squash (bool): Squash the resulting images layers into a
  231. single layer.
  232. extra_hosts (dict): Extra hosts to add to /etc/hosts in building
  233. containers, as a mapping of hostname to IP address.
  234. platform (str): Platform in the format ``os[/arch[/variant]]``.
  235. isolation (str): Isolation technology used during build.
  236. Default: `None`.
  237. use_config_proxy (bool): If ``True``, and if the docker client
  238. configuration file (``~/.docker/config.json`` by default)
  239. contains a proxy configuration, the corresponding environment
  240. variables will be set in the container being built.
  241. Returns:
  242. (tuple): The first item is the :py:class:`Image` object for the
  243. image that was built. The second item is a generator of the
  244. build logs as JSON-decoded objects.
  245. Raises:
  246. :py:class:`docker.errors.BuildError`
  247. If there is an error during the build.
  248. :py:class:`docker.errors.APIError`
  249. If the server returns any other error.
  250. ``TypeError``
  251. If neither ``path`` nor ``fileobj`` is specified.
  252. """
  253. resp = self.client.api.build(**kwargs)
  254. if isinstance(resp, str):
  255. return self.get(resp)
  256. last_event = None
  257. image_id = None
  258. result_stream, internal_stream = itertools.tee(json_stream(resp))
  259. for chunk in internal_stream:
  260. if 'error' in chunk:
  261. raise BuildError(chunk['error'], result_stream)
  262. if 'stream' in chunk:
  263. match = re.search(
  264. r'(^Successfully built |sha256:)([0-9a-f]+)$',
  265. chunk['stream']
  266. )
  267. if match:
  268. image_id = match.group(2)
  269. last_event = chunk
  270. if image_id:
  271. return (self.get(image_id), result_stream)
  272. raise BuildError(last_event or 'Unknown', result_stream)
  273. def get(self, name):
  274. """
  275. Gets an image.
  276. Args:
  277. name (str): The name of the image.
  278. Returns:
  279. (:py:class:`Image`): The image.
  280. Raises:
  281. :py:class:`docker.errors.ImageNotFound`
  282. If the image does not exist.
  283. :py:class:`docker.errors.APIError`
  284. If the server returns an error.
  285. """
  286. return self.prepare_model(self.client.api.inspect_image(name))
  287. def get_registry_data(self, name, auth_config=None):
  288. """
  289. Gets the registry data for an image.
  290. Args:
  291. name (str): The name of the image.
  292. auth_config (dict): Override the credentials that are found in the
  293. config for this request. ``auth_config`` should contain the
  294. ``username`` and ``password`` keys to be valid.
  295. Returns:
  296. (:py:class:`RegistryData`): The data object.
  297. Raises:
  298. :py:class:`docker.errors.APIError`
  299. If the server returns an error.
  300. """
  301. return RegistryData(
  302. image_name=name,
  303. attrs=self.client.api.inspect_distribution(name, auth_config),
  304. client=self.client,
  305. collection=self,
  306. )
  307. def list(self, name=None, all=False, filters=None):
  308. """
  309. List images on the server.
  310. Args:
  311. name (str): Only show images belonging to the repository ``name``
  312. all (bool): Show intermediate image layers. By default, these are
  313. filtered out.
  314. filters (dict): Filters to be processed on the image list.
  315. Available filters:
  316. - ``dangling`` (bool)
  317. - `label` (str|list): format either ``"key"``, ``"key=value"``
  318. or a list of such.
  319. Returns:
  320. (list of :py:class:`Image`): The images.
  321. Raises:
  322. :py:class:`docker.errors.APIError`
  323. If the server returns an error.
  324. """
  325. resp = self.client.api.images(name=name, all=all, filters=filters)
  326. return [self.get(r["Id"]) for r in resp]
  327. def load(self, data):
  328. """
  329. Load an image that was previously saved using
  330. :py:meth:`~docker.models.images.Image.save` (or ``docker save``).
  331. Similar to ``docker load``.
  332. Args:
  333. data (binary): Image data to be loaded.
  334. Returns:
  335. (list of :py:class:`Image`): The images.
  336. Raises:
  337. :py:class:`docker.errors.APIError`
  338. If the server returns an error.
  339. """
  340. resp = self.client.api.load_image(data)
  341. images = []
  342. for chunk in resp:
  343. if 'stream' in chunk:
  344. match = re.search(
  345. r'(^Loaded image ID: |^Loaded image: )(.+)$',
  346. chunk['stream']
  347. )
  348. if match:
  349. image_id = match.group(2)
  350. images.append(image_id)
  351. if 'error' in chunk:
  352. raise ImageLoadError(chunk['error'])
  353. return [self.get(i) for i in images]
  354. def pull(self, repository, tag=None, all_tags=False, **kwargs):
  355. """
  356. Pull an image of the given name and return it. Similar to the
  357. ``docker pull`` command.
  358. If ``tag`` is ``None`` or empty, it is set to ``latest``.
  359. If ``all_tags`` is set, the ``tag`` parameter is ignored and all image
  360. tags will be pulled.
  361. If you want to get the raw pull output, use the
  362. :py:meth:`~docker.api.image.ImageApiMixin.pull` method in the
  363. low-level API.
  364. Args:
  365. repository (str): The repository to pull
  366. tag (str): The tag to pull
  367. auth_config (dict): Override the credentials that are found in the
  368. config for this request. ``auth_config`` should contain the
  369. ``username`` and ``password`` keys to be valid.
  370. platform (str): Platform in the format ``os[/arch[/variant]]``
  371. all_tags (bool): Pull all image tags
  372. Returns:
  373. (:py:class:`Image` or list): The image that has been pulled.
  374. If ``all_tags`` is True, the method will return a list
  375. of :py:class:`Image` objects belonging to this repository.
  376. Raises:
  377. :py:class:`docker.errors.APIError`
  378. If the server returns an error.
  379. Example:
  380. >>> # Pull the image tagged `latest` in the busybox repo
  381. >>> image = client.images.pull('busybox')
  382. >>> # Pull all tags in the busybox repo
  383. >>> images = client.images.pull('busybox', all_tags=True)
  384. """
  385. repository, image_tag = parse_repository_tag(repository)
  386. tag = tag or image_tag or 'latest'
  387. if 'stream' in kwargs:
  388. warnings.warn(
  389. '`stream` is not a valid parameter for this method'
  390. ' and will be overridden'
  391. )
  392. del kwargs['stream']
  393. pull_log = self.client.api.pull(
  394. repository, tag=tag, stream=True, all_tags=all_tags, **kwargs
  395. )
  396. for _ in pull_log:
  397. # We don't do anything with the logs, but we need
  398. # to keep the connection alive and wait for the image
  399. # to be pulled.
  400. pass
  401. if not all_tags:
  402. return self.get('{0}{2}{1}'.format(
  403. repository, tag, '@' if tag.startswith('sha256:') else ':'
  404. ))
  405. return self.list(repository)
  406. def push(self, repository, tag=None, **kwargs):
  407. return self.client.api.push(repository, tag=tag, **kwargs)
  408. push.__doc__ = APIClient.push.__doc__
  409. def remove(self, *args, **kwargs):
  410. self.client.api.remove_image(*args, **kwargs)
  411. remove.__doc__ = APIClient.remove_image.__doc__
  412. def search(self, *args, **kwargs):
  413. return self.client.api.search(*args, **kwargs)
  414. search.__doc__ = APIClient.search.__doc__
  415. def prune(self, filters=None):
  416. return self.client.api.prune_images(filters=filters)
  417. prune.__doc__ = APIClient.prune_images.__doc__
  418. def prune_builds(self, *args, **kwargs):
  419. return self.client.api.prune_builds(*args, **kwargs)
  420. prune_builds.__doc__ = APIClient.prune_builds.__doc__
  421. def normalize_platform(platform, engine_info):
  422. if platform is None:
  423. platform = {}
  424. if 'os' not in platform:
  425. platform['os'] = engine_info['Os']
  426. if 'architecture' not in platform:
  427. platform['architecture'] = engine_info['Arch']
  428. return platform