diff --git a/CHANGES/219.feature b/CHANGES/219.feature new file mode 100644 index 00000000..72370191 --- /dev/null +++ b/CHANGES/219.feature @@ -0,0 +1 @@ +Feature: Add support for docker save and load api methods diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index e6531451..e624068a 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -1,3 +1,4 @@ +Alkim Gozen Andrew Svetlov Anton Patrushev Byeongjun Park @@ -12,4 +13,4 @@ Joshua Chung Martin Koppehel Nikolay Novik Paul Tagliamonte -Tianon Gravi +Tianon Gravi \ No newline at end of file diff --git a/aiodocker/docker.py b/aiodocker/docker.py index 7fe5741c..09ee7113 100644 --- a/aiodocker/docker.py +++ b/aiodocker/docker.py @@ -136,7 +136,7 @@ def _canonicalize_url(self, path): async def _query(self, path, method='GET', *, params=None, data=None, headers=None, - timeout=None): + timeout=None, chunked=None): ''' Get the response object by performing the HTTP request. The caller is responsible to finalize the response object. @@ -150,7 +150,8 @@ async def _query(self, path, method='GET', *, params=httpize(params), headers=headers, data=data, - timeout=timeout) + timeout=timeout, + chunked=chunked) except asyncio.TimeoutError: raise if (response.status // 100) in [4, 5]: @@ -183,6 +184,23 @@ async def _query_json(self, path, method='GET', *, data = await parse_result(response) return data + async def _query_chunked_post(self, path, method='POST', *, + params=None, data=None, headers=None, + timeout=None): + ''' + A shorthand for uploading data by chunks + ''' + if headers is None: + headers = {} + if headers and 'content-type' not in headers: + headers['content-type'] = 'application/octet-stream' + response = await self._query( + path, method, + params=params, data=data, headers=headers, + timeout=timeout, chunked=1024) + data = await parse_result(response) + return data + async def _websocket(self, path, **params): if not params: params = { diff --git a/aiodocker/images.py b/aiodocker/images.py index 763dd60e..588bd4bb 100644 --- a/aiodocker/images.py +++ b/aiodocker/images.py @@ -229,3 +229,40 @@ async def build(self, *, ) return (await json_stream_result(response, stream=stream)) + + async def export_image(self, name: str): + """ + Get a tarball of an image by name or id. + + Args: + name: name/id of the image to be exported + + Returns: + Streamreader of tarball image + """ + response = await self.docker._query( + "images/{name}/get".format(name=name), + "GET", + ) + return response.content + + async def import_image(self, data): + """ + Import tarball of image to docker. + + Args: + data: tarball data of image to be imported + + Returns: + Tarball of the image + """ + headers = { + "Content-Type": "application/x-tar", + } + response = await self.docker._query_chunked_post( + "images/load", + "POST", + data=data, + headers=headers + ) + return response diff --git a/tests/docker/hello-world.img.tar b/tests/docker/hello-world.img.tar new file mode 100644 index 00000000..814ce842 Binary files /dev/null and b/tests/docker/hello-world.img.tar differ diff --git a/tests/test_images.py b/tests/test_images.py index 4aa843c9..761f68ab 100644 --- a/tests/test_images.py +++ b/tests/test_images.py @@ -1,8 +1,10 @@ +import os from io import BytesIO import pytest from aiodocker import utils from aiodocker.exceptions import DockerError +import aiohttp @pytest.mark.asyncio @@ -112,6 +114,33 @@ async def test_build_from_tar(docker, random_name): assert image +@pytest.mark.asyncio +async def test_export_image(docker): + name = "alpine:latest" + exported_image = await docker.images.export_image(name=name) + assert exported_image + + +@pytest.mark.asyncio +async def test_import_image(docker): + + @aiohttp.streamer + async def file_sender(writer, file_name=None): + with open(file_name, 'rb') as f: + chunk = f.read(2**16) + while chunk: + await writer.write(chunk) + chunk = f.read(2**16) + + dir = os.path.dirname(__file__) + hello_world = os.path.join(dir, 'docker/hello-world.img.tar') + response = await docker.images.import_image( + data=file_sender(file_name=hello_world)) + assert 'error' not in response + image = await docker.images.get(name='hello-world:latest') + assert image + + @pytest.mark.asyncio async def test_pups_image_auth(docker): name = "alpine:latest"