Skip to content

Commit b8d8987

Browse files
author
Rakshil Modi
committed
Added no overwrite for download operation
handled error during download streaming streaming
1 parent 64fc091 commit b8d8987

File tree

4 files changed

+171
-0
lines changed

4 files changed

+171
-0
lines changed

awscli/customizations/s3/subcommands.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1603,6 +1603,7 @@ def _validate_streaming_paths(self):
16031603
self.parameters['is_stream'] = True
16041604
self.parameters['dir_op'] = False
16051605
self.parameters['only_show_errors'] = True
1606+
self._validate_streaming_no_overwrite_for_download_parameter()
16061607

16071608
def _validate_path_args(self):
16081609
# If we're using a mv command, you can't copy the object onto itself.
@@ -1824,3 +1825,22 @@ def _validate_sse_c_copy_source_for_paths(self):
18241825
'--sse-c-copy-source is only supported for '
18251826
'copy operations.'
18261827
)
1828+
1829+
def _validate_streaming_no_overwrite_for_download_parameter(self):
1830+
"""
1831+
Validates that no-overwrite parameter is not used with streaming downloads.
1832+
1833+
When downloading from S3 to stdout (streaming download), the no-overwrite
1834+
parameter doesn't make sense since stdout is not a file that can be checked
1835+
for existence. This method checks for this invalid combination and raises
1836+
an appropriate error.
1837+
1838+
Raises:
1839+
ParamValidationError: If no-overwrite is specified with a streaming download.
1840+
"""
1841+
params = self.parameters
1842+
if params['paths_type'] == 's3local' and params.get('no_overwrite'):
1843+
raise ParamValidationError(
1844+
"--no-overwrite parameter is not supported for "
1845+
"streaming downloads"
1846+
)

tests/functional/s3/test_cp_command.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,74 @@ def test_no_overwrite_flag_on_copy_when_large_object_does_not_exist_on_target(
550550
# Verify the IfNoneMatch condition was set in the CompleteMultipartUpload request
551551
self.assertEqual(self.operations_called[5][1]['IfNoneMatch'], '*')
552552

553+
def test_no_overwrite_flag_on_download_when_small_object_already_exists_at_target(
554+
self,
555+
):
556+
full_path = self.files.create_file('foo.txt', 'existing content')
557+
cmdline = (
558+
f'{self.prefix} s3://bucket/foo.txt {full_path} --no-overwrite'
559+
)
560+
self.parsed_responses = [
561+
self.head_object_response(),
562+
]
563+
self.run_cmd(cmdline, expected_rc=0)
564+
self.assertEqual(len(self.operations_called), 1)
565+
self.assertEqual(self.operations_called[0][0].name, 'HeadObject')
566+
with open(full_path) as f:
567+
self.assertEqual(f.read(), 'existing content')
568+
569+
def test_no_overwrite_flag_on_download_when_small_object_does_not_exist_at_target(
570+
self,
571+
):
572+
full_path = self.files.full_path('foo.txt')
573+
cmdline = (
574+
f'{self.prefix} s3://bucket/foo.txt {full_path} --no-overwrite'
575+
)
576+
self.parsed_responses = [
577+
self.head_object_response(),
578+
self.get_object_response(),
579+
]
580+
self.run_cmd(cmdline, expected_rc=0)
581+
self.assertEqual(len(self.operations_called), 2)
582+
self.assertEqual(self.operations_called[0][0].name, 'HeadObject')
583+
self.assertEqual(self.operations_called[1][0].name, 'GetObject')
584+
with open(full_path) as f:
585+
self.assertEqual(f.read(), 'foo')
586+
587+
def test_no_overwrite_flag_on_download_when_large_object_exists_at_target(
588+
self,
589+
):
590+
full_path = self.files.create_file('foo.txt', 'existing content')
591+
cmdline = (
592+
f'{self.prefix} s3://bucket/foo.txt {full_path} --no-overwrite'
593+
)
594+
self.parsed_responses = [
595+
self.head_object_response(ContentLength=10 * (1024**2)),
596+
]
597+
self.run_cmd(cmdline, expected_rc=0)
598+
self.assertEqual(len(self.operations_called), 1)
599+
self.assertEqual(self.operations_called[0][0].name, 'HeadObject')
600+
with open(full_path) as f:
601+
self.assertEqual(f.read(), 'existing content')
602+
603+
def test_no_overwrite_flag_on_download_when_large_object_does_not_exist_at_target(
604+
self,
605+
):
606+
full_path = self.files.full_path('foo.txt')
607+
cmdline = (
608+
f'{self.prefix} s3://bucket/foo.txt {full_path} --no-overwrite'
609+
)
610+
self.parsed_responses = [
611+
self.head_object_response(ContentLength=10 * (1024**2)),
612+
self.get_object_response(),
613+
self.get_object_response(),
614+
]
615+
self.run_cmd(cmdline, expected_rc=0)
616+
self.assertEqual(len(self.operations_called), 3)
617+
self.assertEqual(self.operations_called[0][0].name, 'HeadObject')
618+
self.assertEqual(self.operations_called[1][0].name, 'GetObject')
619+
self.assertEqual(self.operations_called[2][0].name, 'GetObject')
620+
553621
def test_dryrun_download(self):
554622
self.parsed_responses = [self.head_object_response()]
555623
target = self.files.full_path('file.txt')

tests/functional/s3/test_mv_command.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,78 @@ def test_mv_no_overwrite_flag_when_large_object_does_not_exist_on_target(
575575
)
576576
self.assertEqual(self.operations_called[6][0].name, 'DeleteObject')
577577

578+
def test_no_overwrite_flag_on_mv_download_when_small_object_exists_at_target(
579+
self,
580+
):
581+
full_path = self.files.create_file('foo.txt', 'existing content')
582+
cmdline = (
583+
f'{self.prefix} s3://bucket/foo.txt {full_path} --no-overwrite'
584+
)
585+
self.parsed_responses = [
586+
self.head_object_response(),
587+
]
588+
self.run_cmd(cmdline, expected_rc=0)
589+
self.assertEqual(len(self.operations_called), 1)
590+
self.assertEqual(self.operations_called[0][0].name, 'HeadObject')
591+
with open(full_path) as f:
592+
self.assertEqual(f.read(), 'existing content')
593+
594+
def test_no_overwrite_flag_on_mv_download_when_small_object_does_not_exist_at_target(
595+
self,
596+
):
597+
full_path = self.files.full_path('foo.txt')
598+
cmdline = (
599+
f'{self.prefix} s3://bucket/foo.txt {full_path} --no-overwrite'
600+
)
601+
self.parsed_responses = [
602+
self.head_object_response(),
603+
self.get_object_response(),
604+
self.delete_object_response(),
605+
]
606+
self.run_cmd(cmdline, expected_rc=0)
607+
self.assertEqual(len(self.operations_called), 3)
608+
self.assertEqual(self.operations_called[0][0].name, 'HeadObject')
609+
self.assertEqual(self.operations_called[1][0].name, 'GetObject')
610+
self.assertEqual(self.operations_called[2][0].name, 'DeleteObject')
611+
with open(full_path) as f:
612+
self.assertEqual(f.read(), 'foo')
613+
614+
def test_no_overwrite_flag_on_mv_download_when_large_object_exists_at_target(
615+
self,
616+
):
617+
full_path = self.files.create_file('foo.txt', 'existing content')
618+
cmdline = (
619+
f'{self.prefix} s3://bucket/foo.txt {full_path} --no-overwrite'
620+
)
621+
self.parsed_responses = [
622+
self.head_object_response(ContentLength=10 * (1024**2)),
623+
]
624+
self.run_cmd(cmdline, expected_rc=0)
625+
self.assertEqual(len(self.operations_called), 1)
626+
self.assertEqual(self.operations_called[0][0].name, 'HeadObject')
627+
with open(full_path) as f:
628+
self.assertEqual(f.read(), 'existing content')
629+
630+
def test_no_overwrite_flag_on_mv_download_when_large_object_does_not_exist_at_target(
631+
self,
632+
):
633+
full_path = self.files.full_path('foo.txt')
634+
cmdline = (
635+
f'{self.prefix} s3://bucket/foo.txt {full_path} --no-overwrite'
636+
)
637+
self.parsed_responses = [
638+
self.head_object_response(ContentLength=10 * (1024**2)),
639+
self.get_object_response(),
640+
self.get_object_response(),
641+
self.delete_object_response(),
642+
]
643+
self.run_cmd(cmdline, expected_rc=0)
644+
self.assertEqual(len(self.operations_called), 4)
645+
self.assertEqual(self.operations_called[0][0].name, 'HeadObject')
646+
self.assertEqual(self.operations_called[1][0].name, 'GetObject')
647+
self.assertEqual(self.operations_called[2][0].name, 'GetObject')
648+
self.assertEqual(self.operations_called[3][0].name, 'DeleteObject')
649+
578650

579651
class TestMvWithCRTClient(BaseCRTTransferClientTest):
580652
def test_upload_move_using_crt_client(self):

tests/unit/customizations/s3/test_subcommands.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,10 +524,21 @@ def test_validate_streaming_paths_download(self):
524524
paths = ['s3://bucket/key', '-']
525525
cmd_params = CommandParameters('cp', {}, '')
526526
cmd_params.add_paths(paths)
527+
print(" Paramertes %s", cmd_params.parameters)
527528
self.assertTrue(cmd_params.parameters['is_stream'])
528529
self.assertTrue(cmd_params.parameters['only_show_errors'])
529530
self.assertFalse(cmd_params.parameters['dir_op'])
530531

532+
def test_validate_streaming_paths_with_no_overwrite(self):
533+
paths = ['s3://bucket/key', '-']
534+
cmd_params = CommandParameters('cp', {'no_overwrite': True}, '')
535+
with self.assertRaises(ParamValidationError) as cm:
536+
cmd_params.add_paths(paths)
537+
self.assertIn(
538+
'--no-overwrite parameter is not supported for streaming downloads',
539+
cm.msg,
540+
)
541+
531542
def test_validate_no_streaming_paths(self):
532543
paths = [self.file_creator.rootdir, 's3://bucket']
533544
cmd_params = CommandParameters('cp', {}, '')

0 commit comments

Comments
 (0)