From c04d457e08e75fd91202f811df1c68f5646d31be Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Sat, 1 Sep 2018 22:50:10 +0000 Subject: [PATCH] Encode/Decode data when copying files This is the client side change of server patch [1]. If files are copied to container, the file data will be encoded at client side and decoded at server side. If files are copied from container, the file data will be encoded at server side and decoded at client side. [1] https://review.openstack.org/#/c/599202/ Depends-On: https://review.openstack.org/#/c/599202/ Change-Id: Id9fe25e06052d46dcc33a1e05100ef7a2d7db018 Closes-Bug: #1789777 --- zunclient/api_versions.py | 2 +- zunclient/common/utils.py | 17 +++++++++++++++++ zunclient/osc/v1/containers.py | 2 +- zunclient/tests/unit/test_shell.py | 8 ++++---- zunclient/tests/unit/v1/test_containers.py | 15 ++++++++------- zunclient/v1/containers.py | 13 +++++++++++-- zunclient/v1/containers_shell.py | 2 +- 7 files changed, 43 insertions(+), 16 deletions(-) diff --git a/zunclient/api_versions.py b/zunclient/api_versions.py index 87930c3d..29cb31e4 100644 --- a/zunclient/api_versions.py +++ b/zunclient/api_versions.py @@ -31,7 +31,7 @@ if not LOG.handlers: HEADER_NAME = "OpenStack-API-Version" SERVICE_TYPE = "container" MIN_API_VERSION = '1.1' -MAX_API_VERSION = '1.24' +MAX_API_VERSION = '1.25' DEFAULT_API_VERSION = MAX_API_VERSION _SUBSTITUTIONS = {} diff --git a/zunclient/common/utils.py b/zunclient/common/utils.py index cfef2f17..70176c3a 100644 --- a/zunclient/common/utils.py +++ b/zunclient/common/utils.py @@ -14,11 +14,14 @@ # License for the specific language governing permissions and limitations # under the License. +import base64 +import binascii import json import os import re from oslo_utils import netutils +import six from six.moves.urllib import parse from six.moves.urllib import request from zunclient.common.apiclient import exceptions as apiexec @@ -364,3 +367,17 @@ def list_container_networks(networks): utils.print_list(networks, columns, {'fixed_ips': format_network_fixed_ips}, sortby_index=None) + + +def encode_file_data(data): + if six.PY3 and isinstance(data, str): + data = data.encode('utf-8') + return base64.b64encode(data).decode('utf-8') + + +def decode_file_data(data): + # Py3 raises binascii.Error instead of TypeError as in Py27 + try: + return base64.b64decode(data) + except (TypeError, binascii.Error): + raise exc.CommandError(_('Invalid Base 64 file data.')) diff --git a/zunclient/osc/v1/containers.py b/zunclient/osc/v1/containers.py index 95d4075e..10aff442 100644 --- a/zunclient/osc/v1/containers.py +++ b/zunclient/osc/v1/containers.py @@ -1068,7 +1068,7 @@ class CopyContainer(command.Command): res = client.containers.get_archive(**opts) dest_path = parsed_args.destination - tardata = io.BytesIO(res['data'].encode()) + tardata = io.BytesIO(res['data']) with closing(tarfile.open(fileobj=tardata)) as tar: tar.extractall(dest_path) diff --git a/zunclient/tests/unit/test_shell.py b/zunclient/tests/unit/test_shell.py index e0e8ed4f..d0da26fe 100644 --- a/zunclient/tests/unit/test_shell.py +++ b/zunclient/tests/unit/test_shell.py @@ -246,7 +246,7 @@ class ShellTest(utils.TestCase): project_domain_id='', project_domain_name='', user_domain_id='', user_domain_name='', profile=None, endpoint_override=None, insecure=False, cacert=None, - version=api_versions.APIVersion('1.24')) + version=api_versions.APIVersion('1.25')) def test_main_option_region(self): self.make_env() @@ -274,7 +274,7 @@ class ShellTest(utils.TestCase): project_domain_id='', project_domain_name='', user_domain_id='', user_domain_name='', profile=None, endpoint_override=None, insecure=False, cacert=None, - version=api_versions.APIVersion('1.24')) + version=api_versions.APIVersion('1.25')) @mock.patch('zunclient.client.Client') def test_main_endpoint_internal(self, mock_client): @@ -288,7 +288,7 @@ class ShellTest(utils.TestCase): project_domain_id='', project_domain_name='', user_domain_id='', user_domain_name='', profile=None, endpoint_override=None, insecure=False, cacert=None, - version=api_versions.APIVersion('1.24')) + version=api_versions.APIVersion('1.25')) class ShellTestKeystoneV3(ShellTest): @@ -320,4 +320,4 @@ class ShellTestKeystoneV3(ShellTest): user_domain_id='', user_domain_name='Default', endpoint_override=None, insecure=False, profile=None, cacert=None, - version=api_versions.APIVersion('1.24')) + version=api_versions.APIVersion('1.25')) diff --git a/zunclient/tests/unit/v1/test_containers.py b/zunclient/tests/unit/v1/test_containers.py index a56460e6..ad8a5113 100644 --- a/zunclient/tests/unit/v1/test_containers.py +++ b/zunclient/tests/unit/v1/test_containers.py @@ -14,6 +14,7 @@ import copy from six.moves.urllib import parse import testtools from testtools import matchers +from zunclient.common import utils as zun_utils from zunclient import exceptions from zunclient.tests.unit import utils from zunclient.v1 import containers @@ -289,7 +290,7 @@ fake_responses = { { 'GET': ( {}, - None, + {'data': data}, ), }, '/v1/containers/%s/put_archive?%s' @@ -297,7 +298,7 @@ fake_responses = { { 'POST': ( {}, - {'data': data}, + None, ), }, '/v1/containers/%s/stats?%s' @@ -653,25 +654,25 @@ class ContainerManagerTest(testtools.TestCase): self.assertIsNone(containers) def test_containers_get_archive(self): - containers = self.mgr.get_archive(CONTAINER1['id'], path) + response = self.mgr.get_archive(CONTAINER1['id'], path) expect = [ ('GET', '/v1/containers/%s/get_archive?%s' % (CONTAINER1['id'], parse.urlencode({'path': path})), {'Content-Length': '0'}, None) ] self.assertEqual(expect, self.api.calls) - self.assertIsNone(containers) + self.assertEqual(zun_utils.decode_file_data(data), response['data']) def test_containers_put_archive(self): - containers = self.mgr.put_archive(CONTAINER1['id'], path, data) + response = self.mgr.put_archive(CONTAINER1['id'], path, data) expect = [ ('POST', '/v1/containers/%s/put_archive?%s' % (CONTAINER1['id'], parse.urlencode({'path': path})), {'Content-Length': '0'}, - {'data': data}) + {'data': zun_utils.encode_file_data(data)}) ] self.assertEqual(expect, self.api.calls) - self.assertTrue(containers) + self.assertTrue(response) def test_containers_commit(self): containers = self.mgr.commit(CONTAINER1['id'], repo, tag) diff --git a/zunclient/v1/containers.py b/zunclient/v1/containers.py index 14e26409..a187d1dd 100644 --- a/zunclient/v1/containers.py +++ b/zunclient/v1/containers.py @@ -196,10 +196,19 @@ class ContainerManager(base.Manager): qparams={'ps_args': ps_args})[1] def get_archive(self, id, path): - return self._action(id, '/get_archive', method='GET', - qparams={'path': path})[1] + res = self._action(id, '/get_archive', method='GET', + qparams={'path': path})[1] + # API version 1.25 or later will return Base64-encoded data + if self.api_version >= api_versions.APIVersion("1.25"): + res['data'] = utils.decode_file_data(res['data']) + else: + res['data'] = res['data'].encode() + return res def put_archive(self, id, path, data): + # API version 1.25 or later will expect Base64-encoded data + if self.api_version >= api_versions.APIVersion("1.25"): + data = utils.encode_file_data(data) return self._action(id, '/put_archive', qparams={'path': path}, body={'data': data}) diff --git a/zunclient/v1/containers_shell.py b/zunclient/v1/containers_shell.py index 5cf0238f..5715cabf 100644 --- a/zunclient/v1/containers_shell.py +++ b/zunclient/v1/containers_shell.py @@ -865,7 +865,7 @@ def do_cp(cs, args): res = cs.containers.get_archive(**opts) dest_path = args.destination - tardata = io.BytesIO(res['data'].encode()) + tardata = io.BytesIO(res['data']) with closing(tarfile.open(fileobj=tardata)) as tar: tar.extractall(dest_path)