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.

image.py 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. import logging
  2. import os
  3. from .. import auth, errors, utils
  4. from ..constants import DEFAULT_DATA_CHUNK_SIZE
  5. log = logging.getLogger(__name__)
  6. class ImageApiMixin:
  7. @utils.check_resource('image')
  8. def get_image(self, image, chunk_size=DEFAULT_DATA_CHUNK_SIZE):
  9. """
  10. Get a tarball of an image. Similar to the ``docker save`` command.
  11. Args:
  12. image (str): Image name to get
  13. chunk_size (int): The number of bytes returned by each iteration
  14. of the generator. If ``None``, data will be streamed as it is
  15. received. Default: 2 MB
  16. Returns:
  17. (generator): A stream of raw archive data.
  18. Raises:
  19. :py:class:`docker.errors.APIError`
  20. If the server returns an error.
  21. Example:
  22. >>> image = client.api.get_image("busybox:latest")
  23. >>> f = open('/tmp/busybox-latest.tar', 'wb')
  24. >>> for chunk in image:
  25. >>> f.write(chunk)
  26. >>> f.close()
  27. """
  28. res = self._get(self._url("/images/{0}/get", image), stream=True)
  29. return self._stream_raw_result(res, chunk_size, False)
  30. @utils.check_resource('image')
  31. def history(self, image):
  32. """
  33. Show the history of an image.
  34. Args:
  35. image (str): The image to show history for
  36. Returns:
  37. (str): The history of the image
  38. Raises:
  39. :py:class:`docker.errors.APIError`
  40. If the server returns an error.
  41. """
  42. res = self._get(self._url("/images/{0}/history", image))
  43. return self._result(res, True)
  44. def images(self, name=None, quiet=False, all=False, filters=None):
  45. """
  46. List images. Similar to the ``docker images`` command.
  47. Args:
  48. name (str): Only show images belonging to the repository ``name``
  49. quiet (bool): Only return numeric IDs as a list.
  50. all (bool): Show intermediate image layers. By default, these are
  51. filtered out.
  52. filters (dict): Filters to be processed on the image list.
  53. Available filters:
  54. - ``dangling`` (bool)
  55. - `label` (str|list): format either ``"key"``, ``"key=value"``
  56. or a list of such.
  57. Returns:
  58. (dict or list): A list if ``quiet=True``, otherwise a dict.
  59. Raises:
  60. :py:class:`docker.errors.APIError`
  61. If the server returns an error.
  62. """
  63. params = {
  64. 'only_ids': 1 if quiet else 0,
  65. 'all': 1 if all else 0,
  66. }
  67. if name:
  68. if utils.version_lt(self._version, '1.25'):
  69. # only use "filter" on API 1.24 and under, as it is deprecated
  70. params['filter'] = name
  71. else:
  72. if filters:
  73. filters['reference'] = name
  74. else:
  75. filters = {'reference': name}
  76. if filters:
  77. params['filters'] = utils.convert_filters(filters)
  78. res = self._result(self._get(self._url("/images/json"), params=params),
  79. True)
  80. if quiet:
  81. return [x['Id'] for x in res]
  82. return res
  83. def import_image(self, src=None, repository=None, tag=None, image=None,
  84. changes=None, stream_src=False):
  85. """
  86. Import an image. Similar to the ``docker import`` command.
  87. If ``src`` is a string or unicode string, it will first be treated as a
  88. path to a tarball on the local system. If there is an error reading
  89. from that file, ``src`` will be treated as a URL instead to fetch the
  90. image from. You can also pass an open file handle as ``src``, in which
  91. case the data will be read from that file.
  92. If ``src`` is unset but ``image`` is set, the ``image`` parameter will
  93. be taken as the name of an existing image to import from.
  94. Args:
  95. src (str or file): Path to tarfile, URL, or file-like object
  96. repository (str): The repository to create
  97. tag (str): The tag to apply
  98. image (str): Use another image like the ``FROM`` Dockerfile
  99. parameter
  100. """
  101. if not (src or image):
  102. raise errors.DockerException(
  103. 'Must specify src or image to import from'
  104. )
  105. u = self._url('/images/create')
  106. params = _import_image_params(
  107. repository, tag, image,
  108. src=(src if isinstance(src, str) else None),
  109. changes=changes
  110. )
  111. headers = {'Content-Type': 'application/tar'}
  112. if image or params.get('fromSrc') != '-': # from image or URL
  113. return self._result(
  114. self._post(u, data=None, params=params)
  115. )
  116. elif isinstance(src, str): # from file path
  117. with open(src, 'rb') as f:
  118. return self._result(
  119. self._post(
  120. u, data=f, params=params, headers=headers, timeout=None
  121. )
  122. )
  123. else: # from raw data
  124. if stream_src:
  125. headers['Transfer-Encoding'] = 'chunked'
  126. return self._result(
  127. self._post(u, data=src, params=params, headers=headers)
  128. )
  129. def import_image_from_data(self, data, repository=None, tag=None,
  130. changes=None):
  131. """
  132. Like :py:meth:`~docker.api.image.ImageApiMixin.import_image`, but
  133. allows importing in-memory bytes data.
  134. Args:
  135. data (bytes collection): Bytes collection containing valid tar data
  136. repository (str): The repository to create
  137. tag (str): The tag to apply
  138. """
  139. u = self._url('/images/create')
  140. params = _import_image_params(
  141. repository, tag, src='-', changes=changes
  142. )
  143. headers = {'Content-Type': 'application/tar'}
  144. return self._result(
  145. self._post(
  146. u, data=data, params=params, headers=headers, timeout=None
  147. )
  148. )
  149. def import_image_from_file(self, filename, repository=None, tag=None,
  150. changes=None):
  151. """
  152. Like :py:meth:`~docker.api.image.ImageApiMixin.import_image`, but only
  153. supports importing from a tar file on disk.
  154. Args:
  155. filename (str): Full path to a tar file.
  156. repository (str): The repository to create
  157. tag (str): The tag to apply
  158. Raises:
  159. IOError: File does not exist.
  160. """
  161. return self.import_image(
  162. src=filename, repository=repository, tag=tag, changes=changes
  163. )
  164. def import_image_from_stream(self, stream, repository=None, tag=None,
  165. changes=None):
  166. return self.import_image(
  167. src=stream, stream_src=True, repository=repository, tag=tag,
  168. changes=changes
  169. )
  170. def import_image_from_url(self, url, repository=None, tag=None,
  171. changes=None):
  172. """
  173. Like :py:meth:`~docker.api.image.ImageApiMixin.import_image`, but only
  174. supports importing from a URL.
  175. Args:
  176. url (str): A URL pointing to a tar file.
  177. repository (str): The repository to create
  178. tag (str): The tag to apply
  179. """
  180. return self.import_image(
  181. src=url, repository=repository, tag=tag, changes=changes
  182. )
  183. def import_image_from_image(self, image, repository=None, tag=None,
  184. changes=None):
  185. """
  186. Like :py:meth:`~docker.api.image.ImageApiMixin.import_image`, but only
  187. supports importing from another image, like the ``FROM`` Dockerfile
  188. parameter.
  189. Args:
  190. image (str): Image name to import from
  191. repository (str): The repository to create
  192. tag (str): The tag to apply
  193. """
  194. return self.import_image(
  195. image=image, repository=repository, tag=tag, changes=changes
  196. )
  197. @utils.check_resource('image')
  198. def inspect_image(self, image):
  199. """
  200. Get detailed information about an image. Similar to the ``docker
  201. inspect`` command, but only for images.
  202. Args:
  203. image (str): The image to inspect
  204. Returns:
  205. (dict): Similar to the output of ``docker inspect``, but as a
  206. single dict
  207. Raises:
  208. :py:class:`docker.errors.APIError`
  209. If the server returns an error.
  210. """
  211. return self._result(
  212. self._get(self._url("/images/{0}/json", image)), True
  213. )
  214. @utils.minimum_version('1.30')
  215. @utils.check_resource('image')
  216. def inspect_distribution(self, image, auth_config=None):
  217. """
  218. Get image digest and platform information by contacting the registry.
  219. Args:
  220. image (str): The image name to inspect
  221. auth_config (dict): Override the credentials that are found in the
  222. config for this request. ``auth_config`` should contain the
  223. ``username`` and ``password`` keys to be valid.
  224. Returns:
  225. (dict): A dict containing distribution data
  226. Raises:
  227. :py:class:`docker.errors.APIError`
  228. If the server returns an error.
  229. """
  230. registry, _ = auth.resolve_repository_name(image)
  231. headers = {}
  232. if auth_config is None:
  233. header = auth.get_config_header(self, registry)
  234. if header:
  235. headers['X-Registry-Auth'] = header
  236. else:
  237. log.debug('Sending supplied auth config')
  238. headers['X-Registry-Auth'] = auth.encode_header(auth_config)
  239. url = self._url("/distribution/{0}/json", image)
  240. return self._result(
  241. self._get(url, headers=headers), True
  242. )
  243. def load_image(self, data, quiet=None):
  244. """
  245. Load an image that was previously saved using
  246. :py:meth:`~docker.api.image.ImageApiMixin.get_image` (or ``docker
  247. save``). Similar to ``docker load``.
  248. Args:
  249. data (binary): Image data to be loaded.
  250. quiet (boolean): Suppress progress details in response.
  251. Returns:
  252. (generator): Progress output as JSON objects. Only available for
  253. API version >= 1.23
  254. Raises:
  255. :py:class:`docker.errors.APIError`
  256. If the server returns an error.
  257. """
  258. params = {}
  259. if quiet is not None:
  260. if utils.version_lt(self._version, '1.23'):
  261. raise errors.InvalidVersion(
  262. 'quiet is not supported in API version < 1.23'
  263. )
  264. params['quiet'] = quiet
  265. res = self._post(
  266. self._url("/images/load"), data=data, params=params, stream=True
  267. )
  268. if utils.version_gte(self._version, '1.23'):
  269. return self._stream_helper(res, decode=True)
  270. self._raise_for_status(res)
  271. @utils.minimum_version('1.25')
  272. def prune_images(self, filters=None):
  273. """
  274. Delete unused images
  275. Args:
  276. filters (dict): Filters to process on the prune list.
  277. Available filters:
  278. - dangling (bool): When set to true (or 1), prune only
  279. unused and untagged images.
  280. Returns:
  281. (dict): A dict containing a list of deleted image IDs and
  282. the amount of disk space reclaimed in bytes.
  283. Raises:
  284. :py:class:`docker.errors.APIError`
  285. If the server returns an error.
  286. """
  287. url = self._url("/images/prune")
  288. params = {}
  289. if filters is not None:
  290. params['filters'] = utils.convert_filters(filters)
  291. return self._result(self._post(url, params=params), True)
  292. def pull(self, repository, tag=None, stream=False, auth_config=None,
  293. decode=False, platform=None, all_tags=False):
  294. """
  295. Pulls an image. Similar to the ``docker pull`` command.
  296. Args:
  297. repository (str): The repository to pull
  298. tag (str): The tag to pull. If ``tag`` is ``None`` or empty, it
  299. is set to ``latest``.
  300. stream (bool): Stream the output as a generator. Make sure to
  301. consume the generator, otherwise pull might get cancelled.
  302. auth_config (dict): Override the credentials that are found in the
  303. config for this request. ``auth_config`` should contain the
  304. ``username`` and ``password`` keys to be valid.
  305. decode (bool): Decode the JSON data from the server into dicts.
  306. Only applies with ``stream=True``
  307. platform (str): Platform in the format ``os[/arch[/variant]]``
  308. all_tags (bool): Pull all image tags, the ``tag`` parameter is
  309. ignored.
  310. Returns:
  311. (generator or str): The output
  312. Raises:
  313. :py:class:`docker.errors.APIError`
  314. If the server returns an error.
  315. Example:
  316. >>> resp = client.api.pull('busybox', stream=True, decode=True)
  317. ... for line in resp:
  318. ... print(json.dumps(line, indent=4))
  319. {
  320. "status": "Pulling image (latest) from busybox",
  321. "progressDetail": {},
  322. "id": "e72ac664f4f0"
  323. }
  324. {
  325. "status": "Pulling image (latest) from busybox, endpoint: ...",
  326. "progressDetail": {},
  327. "id": "e72ac664f4f0"
  328. }
  329. """
  330. repository, image_tag = utils.parse_repository_tag(repository)
  331. tag = tag or image_tag or 'latest'
  332. if all_tags:
  333. tag = None
  334. registry, repo_name = auth.resolve_repository_name(repository)
  335. params = {
  336. 'tag': tag,
  337. 'fromImage': repository
  338. }
  339. headers = {}
  340. if auth_config is None:
  341. header = auth.get_config_header(self, registry)
  342. if header:
  343. headers['X-Registry-Auth'] = header
  344. else:
  345. log.debug('Sending supplied auth config')
  346. headers['X-Registry-Auth'] = auth.encode_header(auth_config)
  347. if platform is not None:
  348. if utils.version_lt(self._version, '1.32'):
  349. raise errors.InvalidVersion(
  350. 'platform was only introduced in API version 1.32'
  351. )
  352. params['platform'] = platform
  353. response = self._post(
  354. self._url('/images/create'), params=params, headers=headers,
  355. stream=stream, timeout=None
  356. )
  357. self._raise_for_status(response)
  358. if stream:
  359. return self._stream_helper(response, decode=decode)
  360. return self._result(response)
  361. def push(self, repository, tag=None, stream=False, auth_config=None,
  362. decode=False):
  363. """
  364. Push an image or a repository to the registry. Similar to the ``docker
  365. push`` command.
  366. Args:
  367. repository (str): The repository to push to
  368. tag (str): An optional tag to push
  369. stream (bool): Stream the output as a blocking generator
  370. auth_config (dict): Override the credentials that are found in the
  371. config for this request. ``auth_config`` should contain the
  372. ``username`` and ``password`` keys to be valid.
  373. decode (bool): Decode the JSON data from the server into dicts.
  374. Only applies with ``stream=True``
  375. Returns:
  376. (generator or str): The output from the server.
  377. Raises:
  378. :py:class:`docker.errors.APIError`
  379. If the server returns an error.
  380. Example:
  381. >>> resp = client.api.push(
  382. ... 'yourname/app',
  383. ... stream=True,
  384. ... decode=True,
  385. ... )
  386. ... for line in resp:
  387. ... print(line)
  388. {'status': 'Pushing repository yourname/app (1 tags)'}
  389. {'status': 'Pushing','progressDetail': {}, 'id': '511136ea3c5a'}
  390. {'status': 'Image already pushed, skipping', 'progressDetail':{},
  391. 'id': '511136ea3c5a'}
  392. ...
  393. """
  394. if not tag:
  395. repository, tag = utils.parse_repository_tag(repository)
  396. registry, repo_name = auth.resolve_repository_name(repository)
  397. u = self._url("/images/{0}/push", repository)
  398. params = {
  399. 'tag': tag
  400. }
  401. headers = {}
  402. if auth_config is None:
  403. header = auth.get_config_header(self, registry)
  404. if header:
  405. headers['X-Registry-Auth'] = header
  406. else:
  407. log.debug('Sending supplied auth config')
  408. headers['X-Registry-Auth'] = auth.encode_header(auth_config)
  409. response = self._post_json(
  410. u, None, headers=headers, stream=stream, params=params
  411. )
  412. self._raise_for_status(response)
  413. if stream:
  414. return self._stream_helper(response, decode=decode)
  415. return self._result(response)
  416. @utils.check_resource('image')
  417. def remove_image(self, image, force=False, noprune=False):
  418. """
  419. Remove an image. Similar to the ``docker rmi`` command.
  420. Args:
  421. image (str): The image to remove
  422. force (bool): Force removal of the image
  423. noprune (bool): Do not delete untagged parents
  424. """
  425. params = {'force': force, 'noprune': noprune}
  426. res = self._delete(self._url("/images/{0}", image), params=params)
  427. return self._result(res, True)
  428. def search(self, term, limit=None):
  429. """
  430. Search for images on Docker Hub. Similar to the ``docker search``
  431. command.
  432. Args:
  433. term (str): A term to search for.
  434. limit (int): The maximum number of results to return.
  435. Returns:
  436. (list of dicts): The response of the search.
  437. Raises:
  438. :py:class:`docker.errors.APIError`
  439. If the server returns an error.
  440. """
  441. params = {'term': term}
  442. if limit is not None:
  443. params['limit'] = limit
  444. return self._result(
  445. self._get(self._url("/images/search"), params=params),
  446. True
  447. )
  448. @utils.check_resource('image')
  449. def tag(self, image, repository, tag=None, force=False):
  450. """
  451. Tag an image into a repository. Similar to the ``docker tag`` command.
  452. Args:
  453. image (str): The image to tag
  454. repository (str): The repository to set for the tag
  455. tag (str): The tag name
  456. force (bool): Force
  457. Returns:
  458. (bool): ``True`` if successful
  459. Raises:
  460. :py:class:`docker.errors.APIError`
  461. If the server returns an error.
  462. Example:
  463. >>> client.api.tag('ubuntu', 'localhost:5000/ubuntu', 'latest',
  464. force=True)
  465. """
  466. params = {
  467. 'tag': tag,
  468. 'repo': repository,
  469. 'force': 1 if force else 0
  470. }
  471. url = self._url("/images/{0}/tag", image)
  472. res = self._post(url, params=params)
  473. self._raise_for_status(res)
  474. return res.status_code == 201
  475. def is_file(src):
  476. try:
  477. return (
  478. isinstance(src, str) and
  479. os.path.isfile(src)
  480. )
  481. except TypeError: # a data string will make isfile() raise a TypeError
  482. return False
  483. def _import_image_params(repo, tag, image=None, src=None,
  484. changes=None):
  485. params = {
  486. 'repo': repo,
  487. 'tag': tag,
  488. }
  489. if image:
  490. params['fromImage'] = image
  491. elif src and not is_file(src):
  492. params['fromSrc'] = src
  493. else:
  494. params['fromSrc'] = '-'
  495. if changes:
  496. params['changes'] = changes
  497. return params