diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index d793c4599b..3db9311e25 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -17,6 +17,7 @@ import argparse import logging +import sys from glanceclient.common import utils as gc_utils from osc_lib.cli import parseractions @@ -649,6 +650,12 @@ class SaveImage(command.Command): ) data = image_client.images.data(image.id) + if data.wrapped is None: + msg = _('Image %s has no data.') % image.id + LOG.error(msg) + sys.stdout.write(msg + '\n') + raise SystemExit + gc_utils.save_image(data, parsed_args.file) diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 383619ef54..e1a79d13ce 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -15,6 +15,7 @@ import copy +from glanceclient.common import utils as glanceclient_utils from glanceclient.v2 import schemas import mock from osc_lib import exceptions @@ -1505,3 +1506,53 @@ class TestImageUnset(TestImage): self.image.id, 'test' ) self.assertIsNone(result) + + +class TestImageSave(TestImage): + + image = image_fakes.FakeImage.create_one_image({}) + + def setUp(self): + super(TestImageSave, self).setUp() + + # Generate a request id + self.resp = mock.MagicMock() + self.resp.headers['x-openstack-request-id'] = 'req_id' + + # Get the command object to test + self.cmd = image.SaveImage(self.app, None) + + def test_save_data(self): + req_id_proxy = glanceclient_utils.RequestIdProxy( + ['some_data', self.resp] + ) + self.images_mock.data.return_value = req_id_proxy + + arglist = ['--file', '/path/to/file', self.image.id] + + verifylist = [ + ('file', '/path/to/file'), + ('image', self.image.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch('glanceclient.common.utils.save_image') as mocked_save: + self.cmd.take_action(parsed_args) + mocked_save.assert_called_once_with(req_id_proxy, '/path/to/file') + + def test_save_no_data(self): + req_id_proxy = glanceclient_utils.RequestIdProxy( + [None, self.resp] + ) + self.images_mock.data.return_value = req_id_proxy + + arglist = ['--file', '/path/to/file', self.image.id] + + verifylist = [ + ('file', '/path/to/file'), + ('image', self.image.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Raise SystemExit if no data was provided. + self.assertRaises(SystemExit, self.cmd.take_action, parsed_args) diff --git a/releasenotes/notes/bug-1741223-7a5c5b6e7f232628.yaml b/releasenotes/notes/bug-1741223-7a5c5b6e7f232628.yaml new file mode 100644 index 0000000000..2c3a0ef402 --- /dev/null +++ b/releasenotes/notes/bug-1741223-7a5c5b6e7f232628.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + 'NoneType' object is not iterable when Glance cannot find image data in its + backend. + [Bug `1741223 `_]