diff --git a/doc/source/cli/command-objects/object.rst b/doc/source/cli/command-objects/object.rst index 6323c4ed0b..4cba38ee59 100644 --- a/doc/source/cli/command-objects/object.rst +++ b/doc/source/cli/command-objects/object.rst @@ -114,7 +114,8 @@ Save object locally .. option:: --file - Destination filename (defaults to object name) + Destination filename (defaults to object name); + using - as the filename will print the file to stdout .. describe:: diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index 74c4a46f20..3103352503 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -16,6 +16,7 @@ import io import logging import os +import sys from osc_lib import utils import six @@ -376,12 +377,16 @@ class APIv1(api.BaseAPI): stream=True, ) if response.status_code == 200: - if not os.path.exists(os.path.dirname(file)): - if len(os.path.dirname(file)) > 0: - os.makedirs(os.path.dirname(file)) - with open(file, 'wb') as f: + if file == '-': for chunk in response.iter_content(64 * 1024): - f.write(chunk) + sys.stdout.write(chunk) + else: + if not os.path.exists(os.path.dirname(file)): + if len(os.path.dirname(file)) > 0: + os.makedirs(os.path.dirname(file)) + with open(file, 'wb') as f: + for chunk in response.iter_content(64 * 1024): + f.write(chunk) def object_set( self, diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index e79cea48ea..aeb0253653 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -204,7 +204,8 @@ class SaveObject(command.Command): parser.add_argument( "--file", metavar="", - help=_("Destination filename (defaults to object name)"), + help=_("Destination filename (defaults to object name); using '-'" + " as the filename will print the file to stdout"), ) parser.add_argument( 'container', diff --git a/openstackclient/tests/functional/object/v1/test_object.py b/openstackclient/tests/functional/object/v1/test_object.py index 776cf47c2c..0927d706dc 100644 --- a/openstackclient/tests/functional/object/v1/test_object.py +++ b/openstackclient/tests/functional/object/v1/test_object.py @@ -66,6 +66,10 @@ class ObjectTests(base.TestCase): + ' ' + object_file + ' --file ' + tmp_file) # TODO(stevemar): Assert returned fields + raw_output = self.openstack('object save ' + self.CONTAINER_NAME + + ' ' + object_file + ' --file -') + self.assertEqual(raw_output, 'test content') + self.openstack('object show ' + self.CONTAINER_NAME + ' ' + object_file) # TODO(stevemar): Assert returned fields diff --git a/openstackclient/tests/unit/object/v1/fakes.py b/openstackclient/tests/unit/object/v1/fakes.py index 72646d25bb..5d65d1066f 100644 --- a/openstackclient/tests/unit/object/v1/fakes.py +++ b/openstackclient/tests/unit/object/v1/fakes.py @@ -13,6 +13,8 @@ # under the License. # +import six + from keystoneauth1 import session from openstackclient.api import object_store_v1 as object_store @@ -67,6 +69,8 @@ OBJECT = { 'last_modified': object_modified_1, } +object_1_content = six.b('object 1 content') + OBJECT_2 = { 'name': object_name_2, 'bytes': object_bytes_2, diff --git a/openstackclient/tests/unit/object/v1/test_object_all.py b/openstackclient/tests/unit/object/v1/test_object_all.py index f215836ed0..363f2ea21e 100644 --- a/openstackclient/tests/unit/object/v1/test_object_all.py +++ b/openstackclient/tests/unit/object/v1/test_object_all.py @@ -13,8 +13,10 @@ import copy +import mock from osc_lib import exceptions from requests_mock.contrib import fixture +import six from openstackclient.object.v1 import object as object_cmds from openstackclient.tests.unit.object.v1 import fakes as object_fakes @@ -202,3 +204,44 @@ class TestObjectShow(TestObjectAll): 'manifest', ) self.assertEqual(datalist, data) + + +class TestObjectSave(TestObjectAll): + + def setUp(self): + super(TestObjectSave, self).setUp() + + # Get the command object to test + self.cmd = object_cmds.SaveObject(self.app, None) + + def test_save_to_stdout(self): + self.requests_mock.register_uri( + 'GET', + object_fakes.ENDPOINT + + '/' + + object_fakes.container_name + + '/' + + object_fakes.object_name_1, + status_code=200, + content=object_fakes.object_1_content + ) + + arglist = [ + object_fakes.container_name, + object_fakes.object_name_1, + '--file', + '-' + ] + + verifylist = [ + ('container', object_fakes.container_name), + ('object', object_fakes.object_name_1), + ('file', '-'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch('sys.stdout', new=six.BytesIO()) as fake_stdout: + self.cmd.take_action(parsed_args) + + self.assertEqual(fake_stdout.getvalue(), object_fakes.object_1_content) diff --git a/releasenotes/notes/object-stdout-db76cc500948b0e8.yaml b/releasenotes/notes/object-stdout-db76cc500948b0e8.yaml new file mode 100644 index 0000000000..29b21131bc --- /dev/null +++ b/releasenotes/notes/object-stdout-db76cc500948b0e8.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add support for streaming Swift objects to stdout when using the ``object + save`` command, by specifying ``--filename -``.