Skip to content

Commit 3d9a9df

Browse files
committed
image: Simplify handling of data provided via stdin
This was unnecessarily complex. Change-Id: I8289d5ce7356d8bc89425590a7f71bca91a6d396 Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
1 parent bafece7 commit 3d9a9df

2 files changed

Lines changed: 82 additions & 103 deletions

File tree

openstackclient/image/v2/image.py

Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -134,34 +134,32 @@ def _get_member_columns(item):
134134
)
135135

136136

137-
def get_data_file(args):
138-
if args.file:
139-
return (open(args.file, 'rb'), args.file)
137+
def get_data_from_stdin():
138+
# distinguish cases where:
139+
# (1) stdin is not valid (as in cron jobs):
140+
# openstack ... <&-
141+
# (2) image data is provided through stdin:
142+
# openstack ... < /tmp/file
143+
# (3) no image data provided
144+
# openstack ...
145+
try:
146+
os.fstat(0)
147+
except OSError:
148+
# (1) stdin is not valid
149+
return None
150+
151+
if not sys.stdin.isatty():
152+
# (2) image data is provided through stdin
153+
image = sys.stdin
154+
if hasattr(sys.stdin, 'buffer'):
155+
image = sys.stdin.buffer
156+
if msvcrt:
157+
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
158+
159+
return image
140160
else:
141-
# distinguish cases where:
142-
# (1) stdin is not valid (as in cron jobs):
143-
# openstack ... <&-
144-
# (2) image data is provided through stdin:
145-
# openstack ... < /tmp/file
146-
# (3) no image data provided
147-
# openstack ...
148-
try:
149-
os.fstat(0)
150-
except OSError:
151-
# (1) stdin is not valid
152-
return (None, None)
153-
if not sys.stdin.isatty():
154-
# (2) image data is provided through stdin
155-
image = sys.stdin
156-
if hasattr(sys.stdin, 'buffer'):
157-
image = sys.stdin.buffer
158-
if msvcrt:
159-
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
160-
161-
return (image, None)
162-
else:
163-
# (3)
164-
return (None, None)
161+
# (3)
162+
return None
165163

166164

167165
class AddProjectToImage(command.ShowOne):
@@ -277,6 +275,7 @@ def get_parser(self, prog_name):
277275
source_group = parser.add_mutually_exclusive_group()
278276
source_group.add_argument(
279277
"--file",
278+
dest="filename",
280279
metavar="<file>",
281280
help=_("Upload image from local file"),
282281
)
@@ -299,7 +298,10 @@ def get_parser(self, prog_name):
299298
"--progress",
300299
action="store_true",
301300
default=False,
302-
help=_("Show upload progress bar."),
301+
help=_(
302+
"Show upload progress bar "
303+
"(ignored if passing data via stdin)"
304+
),
303305
)
304306
parser.add_argument(
305307
'--sign-key-path',
@@ -463,7 +465,15 @@ def _take_action_image(self, parsed_args):
463465
# open the file first to ensure any failures are handled before the
464466
# image is created. Get the file name (if it is file, and not stdin)
465467
# for easier further handling.
466-
fp, fname = get_data_file(parsed_args)
468+
if parsed_args.filename:
469+
try:
470+
fp = open(parsed_args.filename, 'rb')
471+
except FileNotFoundError:
472+
raise exceptions.CommandError(
473+
'%r is not a valid file' % parsed_args.filename,
474+
)
475+
else:
476+
fp = get_data_from_stdin()
467477

468478
if fp is not None and parsed_args.volume:
469479
msg = _(
@@ -472,26 +482,24 @@ def _take_action_image(self, parsed_args):
472482
)
473483
raise exceptions.CommandError(msg)
474484

475-
if fp is None and parsed_args.file:
476-
LOG.warning(_("Failed to get an image file."))
477-
return {}, {}
478-
479-
if parsed_args.progress and parsed_args.file:
485+
if parsed_args.progress and parsed_args.filename:
480486
# NOTE(stephenfin): we only show a progress bar if the user
481487
# requested it *and* we're reading from a file (not stdin)
482-
filesize = os.path.getsize(fname)
488+
filesize = os.path.getsize(parsed_args.filename)
483489
if filesize is not None:
484490
kwargs['validate_checksum'] = False
485491
kwargs['data'] = progressbar.VerboseFileWrapper(fp, filesize)
486-
elif fname:
487-
kwargs['filename'] = fname
492+
else:
493+
kwargs['data'] = fp
494+
elif parsed_args.filename:
495+
kwargs['filename'] = parsed_args.filename
488496
elif fp:
489497
kwargs['validate_checksum'] = False
490498
kwargs['data'] = fp
491499

492500
# sign an image using a given local private key file
493501
if parsed_args.sign_key_path or parsed_args.sign_cert_id:
494-
if not parsed_args.file:
502+
if not parsed_args.filename:
495503
msg = _(
496504
"signing an image requires the --file option, "
497505
"passing files via stdin when signing is not "
@@ -546,6 +554,10 @@ def _take_action_image(self, parsed_args):
546554
kwargs['img_signature_key_type'] = signer.padding_method
547555

548556
image = image_client.create_image(**kwargs)
557+
558+
if parsed_args.filename:
559+
fp.close()
560+
549561
return _format_image(image)
550562

551563
def _take_action_volume(self, parsed_args):
@@ -980,6 +992,7 @@ def get_parser(self, prog_name):
980992
parser.add_argument(
981993
"--file",
982994
metavar="<filename>",
995+
dest="filename",
983996
help=_("Downloaded image save filename (default: stdout)"),
984997
)
985998
parser.add_argument(
@@ -993,7 +1006,7 @@ def take_action(self, parsed_args):
9931006
image_client = self.app.client_manager.image
9941007
image = image_client.find_image(parsed_args.image)
9951008

996-
output_file = parsed_args.file
1009+
output_file = parsed_args.filename
9971010
if output_file is None:
9981011
output_file = getattr(sys.stdout, "buffer", sys.stdout)
9991012

openstackclient/tests/unit/image/v2/test_image.py

Lines changed: 30 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
import copy
1616
import io
17-
import os
1817
import tempfile
1918
from unittest import mock
2019

@@ -104,15 +103,8 @@ def test_image_reserve_no_options(self, raw_input):
104103
disk_format=image.DEFAULT_DISK_FORMAT,
105104
)
106105

107-
# Verify update() was not called, if it was show the args
108-
self.assertEqual(self.client.update_image.call_args_list, [])
109-
110-
self.assertEqual(
111-
self.expected_columns,
112-
columns)
113-
self.assertCountEqual(
114-
self.expected_data,
115-
data)
106+
self.assertEqual(self.expected_columns, columns)
107+
self.assertCountEqual(self.expected_data, data)
116108

117109
@mock.patch('sys.stdin', side_effect=[None])
118110
def test_image_reserve_options(self, raw_input):
@@ -121,10 +113,11 @@ def test_image_reserve_options(self, raw_input):
121113
'--disk-format', 'ami',
122114
'--min-disk', '10',
123115
'--min-ram', '4',
124-
('--protected'
125-
if self.new_image.is_protected else '--unprotected'),
126-
('--private'
127-
if self.new_image.visibility == 'private' else '--public'),
116+
'--protected' if self.new_image.is_protected else '--unprotected',
117+
(
118+
'--private'
119+
if self.new_image.visibility == 'private' else '--public'
120+
),
128121
'--project', self.new_image.owner_id,
129122
'--project-domain', self.domain.id,
130123
self.new_image.name,
@@ -160,12 +153,8 @@ def test_image_reserve_options(self, raw_input):
160153
visibility=self.new_image.visibility,
161154
)
162155

163-
self.assertEqual(
164-
self.expected_columns,
165-
columns)
166-
self.assertCountEqual(
167-
self.expected_data,
168-
data)
156+
self.assertEqual(self.expected_columns, columns)
157+
self.assertCountEqual(self.expected_data, data)
169158

170159
def test_image_create_with_unexist_project(self):
171160
self.project_mock.get.side_effect = exceptions.NotFound(None)
@@ -217,7 +206,7 @@ def test_image_create_file(self):
217206
self.new_image.name,
218207
]
219208
verifylist = [
220-
('file', imagefile.name),
209+
('filename', imagefile.name),
221210
('is_protected', self.new_image.is_protected),
222211
('visibility', self.new_image.visibility),
223212
('properties', {'Alpha': '1', 'Beta': '2'}),
@@ -252,12 +241,12 @@ def test_image_create_file(self):
252241
self.expected_data,
253242
data)
254243

255-
@mock.patch('openstackclient.image.v2.image.get_data_file')
244+
@mock.patch('openstackclient.image.v2.image.get_data_from_stdin')
256245
def test_image_create__progress_ignore_with_stdin(
257-
self, mock_get_data_file,
246+
self, mock_get_data_from_stdin,
258247
):
259248
fake_stdin = io.StringIO('fake-image-data')
260-
mock_get_data_file.return_value = (fake_stdin, None)
249+
mock_get_data_from_stdin.return_value = fake_stdin
261250

262251
arglist = [
263252
'--progress',
@@ -322,11 +311,11 @@ def test_image_create_import(self, raw_input):
322311
)
323312

324313
@mock.patch('osc_lib.utils.find_resource')
325-
@mock.patch('openstackclient.image.v2.image.get_data_file')
314+
@mock.patch('openstackclient.image.v2.image.get_data_from_stdin')
326315
def test_image_create_from_volume(self, mock_get_data_f, mock_get_vol):
327316

328317
fake_vol_id = 'fake-volume-id'
329-
mock_get_data_f.return_value = (None, None)
318+
mock_get_data_f.return_value = None
330319

331320
class FakeVolume:
332321
id = fake_vol_id
@@ -353,12 +342,12 @@ class FakeVolume:
353342
)
354343

355344
@mock.patch('osc_lib.utils.find_resource')
356-
@mock.patch('openstackclient.image.v2.image.get_data_file')
345+
@mock.patch('openstackclient.image.v2.image.get_data_from_stdin')
357346
def test_image_create_from_volume_fail(self, mock_get_data_f,
358347
mock_get_vol):
359348

360349
fake_vol_id = 'fake-volume-id'
361-
mock_get_data_f.return_value = (None, None)
350+
mock_get_data_f.return_value = None
362351

363352
class FakeVolume:
364353
id = fake_vol_id
@@ -379,15 +368,15 @@ class FakeVolume:
379368
parsed_args)
380369

381370
@mock.patch('osc_lib.utils.find_resource')
382-
@mock.patch('openstackclient.image.v2.image.get_data_file')
371+
@mock.patch('openstackclient.image.v2.image.get_data_from_stdin')
383372
def test_image_create_from_volume_v31(self, mock_get_data_f,
384373
mock_get_vol):
385374

386375
self.app.client_manager.volume.api_version = (
387376
api_versions.APIVersion('3.1'))
388377

389378
fake_vol_id = 'fake-volume-id'
390-
mock_get_data_f.return_value = (None, None)
379+
mock_get_data_f.return_value = None
391380

392381
class FakeVolume:
393382
id = fake_vol_id
@@ -1798,7 +1787,7 @@ def test_save_data(self):
17981787
arglist = ['--file', '/path/to/file', self.image.id]
17991788

18001789
verifylist = [
1801-
('file', '/path/to/file'),
1790+
('filename', '/path/to/file'),
18021791
('image', self.image.id),
18031792
]
18041793
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -1813,49 +1802,26 @@ def test_save_data(self):
18131802

18141803
class TestImageGetData(TestImage):
18151804

1816-
def setUp(self):
1817-
super().setUp()
1818-
self.args = mock.Mock()
1819-
1820-
def test_get_data_file_file(self):
1821-
(fd, fname) = tempfile.mkstemp(prefix='osc_test_image')
1822-
self.args.file = fname
1823-
1824-
(test_fd, test_name) = image.get_data_file(self.args)
1825-
1826-
self.assertEqual(fname, test_name)
1827-
test_fd.close()
1828-
1829-
os.unlink(fname)
1830-
1831-
def test_get_data_file_2(self):
1832-
1833-
self.args.file = None
1834-
1835-
f = io.BytesIO(b"some initial binary data: \x00\x01")
1805+
def test_get_data_from_stdin(self):
1806+
fd = io.BytesIO(b"some initial binary data: \x00\x01")
18361807

18371808
with mock.patch('sys.stdin') as stdin:
1838-
stdin.return_value = f
1809+
stdin.return_value = fd
18391810
stdin.isatty.return_value = False
1840-
stdin.buffer = f
1811+
stdin.buffer = fd
18411812

1842-
(test_fd, test_name) = image.get_data_file(self.args)
1813+
test_fd = image.get_data_from_stdin()
18431814

18441815
# Ensure data written to temp file is correct
1845-
self.assertEqual(f, test_fd)
1846-
self.assertIsNone(test_name)
1847-
1848-
def test_get_data_file_3(self):
1849-
1850-
self.args.file = None
1816+
self.assertEqual(fd, test_fd)
18511817

1852-
f = io.BytesIO(b"some initial binary data: \x00\x01")
1818+
def test_get_data_from_stdin__interactive(self):
1819+
fd = io.BytesIO(b"some initial binary data: \x00\x01")
18531820

18541821
with mock.patch('sys.stdin') as stdin:
18551822
# There is stdin, but interactive
1856-
stdin.return_value = f
1823+
stdin.return_value = fd
18571824

1858-
(test_fd, test_fname) = image.get_data_file(self.args)
1825+
test_fd = image.get_data_from_stdin()
18591826

18601827
self.assertIsNone(test_fd)
1861-
self.assertIsNone(test_fname)

0 commit comments

Comments
 (0)