From 65fb5be4cd30bb98f968986789bad00072b6001a Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 15 Jun 2016 15:54:09 -0700 Subject: [PATCH 1/2] Add support for changes param in import_image* methods Reduce code duplication in import_image* methods Signed-off-by: Joffrey F --- docker/api/image.py | 172 +++++++++++++++++++++++++------------------- 1 file changed, 97 insertions(+), 75 deletions(-) diff --git a/docker/api/image.py b/docker/api/image.py index 4d6561e52d..7f25f9d971 100644 --- a/docker/api/image.py +++ b/docker/api/image.py @@ -1,4 +1,5 @@ import logging +import os import six import warnings @@ -42,87 +43,79 @@ def images(self, name=None, quiet=False, all=False, viz=False, return [x['Id'] for x in res] return res - def import_image(self, src=None, repository=None, tag=None, image=None): - if src: - if isinstance(src, six.string_types): - try: - result = self.import_image_from_file( - src, repository=repository, tag=tag) - except IOError: - result = self.import_image_from_url( - src, repository=repository, tag=tag) - else: - result = self.import_image_from_data( - src, repository=repository, tag=tag) - elif image: - result = self.import_image_from_image( - image, repository=repository, tag=tag) - else: - raise Exception("Must specify a src or image") - - return result - - def import_image_from_data(self, data, repository=None, tag=None): - u = self._url("/images/create") - params = { - 'fromSrc': '-', - 'repo': repository, - 'tag': tag - } - headers = { - 'Content-Type': 'application/tar', - } - return self._result( - self._post(u, data=data, params=params, headers=headers)) + def import_image(self, src=None, repository=None, tag=None, image=None, + changes=None, stream_src=False): + if not (src or image): + raise errors.DockerException( + 'Must specify src or image to import from' + ) + u = self._url('/images/create') - def import_image_from_file(self, filename, repository=None, tag=None): - u = self._url("/images/create") - params = { - 'fromSrc': '-', - 'repo': repository, - 'tag': tag - } - headers = { - 'Content-Type': 'application/tar', - } - with open(filename, 'rb') as f: + params = _import_image_params( + repository, tag, image, + src=(src if isinstance(src, six.string_types) else None), + changes=changes + ) + headers = {'Content-Type': 'application/tar'} + + if image or params.get('fromSrc') != '-': # from image or URL return self._result( - self._post(u, data=f, params=params, headers=headers, - timeout=None)) + self._post(u, data=None, params=params) + ) + elif isinstance(src, six.string_types): # from file path + with open(src, 'rb') as f: + return self._result( + self._post( + u, data=f, params=params, headers=headers, timeout=None + ) + ) + else: # from raw data + if stream_src: + headers['Transfer-Encoding'] = 'chunked' + return self._result( + self._post(u, data=src, params=params, headers=headers) + ) - def import_image_from_stream(self, stream, repository=None, tag=None): - u = self._url("/images/create") - params = { - 'fromSrc': '-', - 'repo': repository, - 'tag': tag - } - headers = { - 'Content-Type': 'application/tar', - 'Transfer-Encoding': 'chunked', - } + def import_image_from_data(self, data, repository=None, tag=None, + changes=None): + u = self._url('/images/create') + params = _import_image_params( + repository, tag, src='-', changes=changes + ) + headers = {'Content-Type': 'application/tar'} return self._result( - self._post(u, data=stream, params=params, headers=headers)) + self._post( + u, data=data, params=params, headers=headers, timeout=None + ) + ) + return self.import_image( + src=data, repository=repository, tag=tag, changes=changes + ) - def import_image_from_url(self, url, repository=None, tag=None): - u = self._url("/images/create") - params = { - 'fromSrc': url, - 'repo': repository, - 'tag': tag - } - return self._result( - self._post(u, data=None, params=params)) + def import_image_from_file(self, filename, repository=None, tag=None, + changes=None): + return self.import_image( + src=filename, repository=repository, tag=tag, changes=changes + ) - def import_image_from_image(self, image, repository=None, tag=None): - u = self._url("/images/create") - params = { - 'fromImage': image, - 'repo': repository, - 'tag': tag - } - return self._result( - self._post(u, data=None, params=params)) + def import_image_from_stream(self, stream, repository=None, tag=None, + changes=None): + return self.import_image( + src=stream, stream_src=True, repository=repository, tag=tag, + changes=changes + ) + + def import_image_from_url(self, url, repository=None, tag=None, + changes=None): + return self.import_image( + src=url, repository=repository, tag=tag, changes=changes + ) + + def import_image_from_image(self, image, repository=None, tag=None, + changes=None): + return self.import_image( + image=image, repository=repository, tag=tag, changes=changes + ) @utils.check_resource def insert(self, image, url, path): @@ -246,3 +239,32 @@ def tag(self, image, repository, tag=None, force=False): res = self._post(url, params=params) self._raise_for_status(res) return res.status_code == 201 + + +def is_file(src): + try: + return ( + isinstance(src, six.string_types) and + os.path.isfile(src) + ) + except TypeError: # a data string will make isfile() raise a TypeError + return False + + +def _import_image_params(repo, tag, image=None, src=None, + changes=None): + params = { + 'repo': repo, + 'tag': tag, + } + if image: + params['fromImage'] = image + elif src and not is_file(src): + params['fromSrc'] = src + else: + params['fromSrc'] = '-' + + if changes: + params['changes'] = changes + + return params From 75497e07528a3d84a6ddd343aa66837ebb304e2a Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 15 Jun 2016 16:39:24 -0700 Subject: [PATCH 2/2] Add test for import_image with changes param Signed-off-by: Joffrey F --- Makefile | 5 ++-- tests/integration/image_test.py | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 3a8f5e8c3f..5083b79ca2 100644 --- a/Makefile +++ b/Makefile @@ -3,9 +3,8 @@ all: test clean: - rm -rf tests/__pycache__ - rm -rf tests/*/__pycache__ - docker rm -vf dpy-dind + -docker rm -vf dpy-dind + find -name "__pycache__" | xargs rm -rf build: docker build -t docker-py . diff --git a/tests/integration/image_test.py b/tests/integration/image_test.py index 9f38366578..a61b58aeb7 100644 --- a/tests/integration/image_test.py +++ b/tests/integration/image_test.py @@ -208,6 +208,48 @@ def test_import_from_stream(self): img_id = result['status'] self.tmp_imgs.append(img_id) + def test_import_image_from_data_with_changes(self): + with self.dummy_tar_stream(n_bytes=500) as f: + content = f.read() + + statuses = self.client.import_image_from_data( + content, repository='test/import-from-bytes', + changes=['USER foobar', 'CMD ["echo"]'] + ) + + result_text = statuses.splitlines()[-1] + result = json.loads(result_text) + + assert 'error' not in result + + img_id = result['status'] + self.tmp_imgs.append(img_id) + + img_data = self.client.inspect_image(img_id) + assert img_data is not None + assert img_data['Config']['Cmd'] == ['echo'] + assert img_data['Config']['User'] == 'foobar' + + def test_import_image_with_changes(self): + with self.dummy_tar_file(n_bytes=self.TAR_SIZE) as tar_filename: + statuses = self.client.import_image( + src=tar_filename, repository='test/import-from-file', + changes=['USER foobar', 'CMD ["echo"]'] + ) + + result_text = statuses.splitlines()[-1] + result = json.loads(result_text) + + assert 'error' not in result + + img_id = result['status'] + self.tmp_imgs.append(img_id) + + img_data = self.client.inspect_image(img_id) + assert img_data is not None + assert img_data['Config']['Cmd'] == ['echo'] + assert img_data['Config']['User'] == 'foobar' + @contextlib.contextmanager def temporary_http_file_server(self, stream): '''Serve data from an IO stream over HTTP.'''