Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aws
GitHub Repository: aws/aws-cli
Path: blob/develop/tests/functional/s3/test_mv_command.py
2634 views
1
#!/usr/bin/env python
2
# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
#
4
# Licensed under the Apache License, Version 2.0 (the "License"). You
5
# may not use this file except in compliance with the License. A copy of
6
# the License is located at
7
#
8
# http://aws.amazon.com/apache2.0/
9
#
10
# or in the "license" file accompanying this file. This file is
11
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12
# ANY KIND, either express or implied. See the License for the specific
13
# language governing permissions and limitations under the License.
14
from awscli.customizations.s3.utils import S3PathResolver
15
from awscli.compat import BytesIO
16
from awscli.testutils import skip_if_case_sensitive
17
from tests.functional.s3 import BaseS3TransferCommandTest
18
from tests.functional.s3.test_sync_command import TestSyncCaseConflict
19
from tests import requires_crt
20
21
22
class TestMvCommand(BaseS3TransferCommandTest):
23
24
prefix = 's3 mv '
25
26
def test_cant_mv_object_onto_itself(self):
27
cmdline = '%s s3://bucket/key s3://bucket/key' % self.prefix
28
stderr = self.run_cmd(cmdline, expected_rc=255)[1]
29
self.assertIn('Cannot mv a file onto itself', stderr)
30
31
def test_cant_mv_object_with_implied_name(self):
32
# The "key" key name is implied in the dst argument.
33
cmdline = '%s s3://bucket/key s3://bucket/' % self.prefix
34
stderr = self.run_cmd(cmdline, expected_rc=255)[1]
35
self.assertIn('Cannot mv a file onto itself', stderr)
36
37
def test_website_redirect_ignore_paramfile(self):
38
full_path = self.files.create_file('foo.txt', 'mycontent')
39
cmdline = '%s %s s3://bucket/key.txt --website-redirect %s' % \
40
(self.prefix, full_path, 'http://someserver')
41
self.parsed_responses = [{'ETag': '"c8afdb36c52cf4727836669019e69222"'}]
42
self.run_cmd(cmdline, expected_rc=0)
43
self.assertEqual(self.operations_called[0][0].name, 'PutObject')
44
# Make sure that the specified web address is used as opposed to the
45
# contents of the web address.
46
self.assertEqual(
47
self.operations_called[0][1]['WebsiteRedirectLocation'],
48
'http://someserver'
49
)
50
51
def test_metadata_directive_copy(self):
52
self.parsed_responses = [
53
{
54
"ContentLength": "100",
55
"LastModified": "00:00:00Z",
56
"ETag": '"foo-1"',
57
},
58
{'ETag': '"foo-1"'},
59
{'ETag': '"foo-2"'}
60
]
61
cmdline = ('%s s3://bucket/key.txt s3://bucket/key2.txt'
62
' --metadata-directive REPLACE' % self.prefix)
63
self.run_cmd(cmdline, expected_rc=0)
64
self.assertEqual(len(self.operations_called), 3,
65
self.operations_called)
66
self.assertEqual(self.operations_called[0][0].name, 'HeadObject')
67
self.assertEqual(self.operations_called[1][0].name, 'CopyObject')
68
self.assertEqual(self.operations_called[2][0].name, 'DeleteObject')
69
self.assertEqual(self.operations_called[1][1]['MetadataDirective'],
70
'REPLACE')
71
72
def test_no_metadata_directive_for_non_copy(self):
73
full_path = self.files.create_file('foo.txt', 'mycontent')
74
cmdline = '%s %s s3://bucket --metadata-directive REPLACE' % \
75
(self.prefix, full_path)
76
self.parsed_responses = \
77
[{'ETag': '"c8afdb36c52cf4727836669019e69222"'}]
78
self.run_cmd(cmdline, expected_rc=0)
79
self.assertEqual(len(self.operations_called), 1,
80
self.operations_called)
81
self.assertEqual(self.operations_called[0][0].name, 'PutObject')
82
self.assertNotIn('MetadataDirective', self.operations_called[0][1])
83
84
def test_download_move_with_request_payer(self):
85
cmdline = '%s s3://mybucket/mykey %s --request-payer' % (
86
self.prefix, self.files.rootdir)
87
88
self.parsed_responses = [
89
# Response for HeadObject
90
{
91
"ContentLength": 100,
92
"LastModified": "00:00:00Z",
93
"ETag": '"foo-1"',
94
},
95
# Response for GetObject
96
{'ETag': '"foo-1"', 'Body': BytesIO(b'foo')},
97
# Response for DeleteObject
98
{}
99
]
100
101
self.run_cmd(cmdline, expected_rc=0)
102
self.assert_operations_called(
103
[
104
('HeadObject', {
105
'Bucket': 'mybucket',
106
'Key': 'mykey',
107
'RequestPayer': 'requester',
108
}),
109
('GetObject', {
110
'Bucket': 'mybucket',
111
'Key': 'mykey',
112
'RequestPayer': 'requester',
113
}),
114
('DeleteObject', {
115
'Bucket': 'mybucket',
116
'Key': 'mykey',
117
'RequestPayer': 'requester',
118
})
119
]
120
)
121
122
def test_copy_move_with_request_payer(self):
123
cmdline = self.prefix
124
cmdline += 's3://sourcebucket/sourcekey s3://mybucket/mykey'
125
cmdline += ' --request-payer'
126
127
self.parsed_responses = [
128
self.head_object_response(),
129
self.copy_object_response(),
130
self.delete_object_response(),
131
]
132
self.run_cmd(cmdline, expected_rc=0)
133
self.assert_operations_called(
134
[
135
self.head_object_request(
136
'sourcebucket', 'sourcekey', RequestPayer='requester'),
137
self.copy_object_request(
138
'sourcebucket', 'sourcekey', 'mybucket', 'mykey',
139
RequestPayer='requester'),
140
self.delete_object_request(
141
'sourcebucket', 'sourcekey', RequestPayer='requester')
142
]
143
)
144
145
def test_upload_with_checksum_algorithm_crc32(self):
146
full_path = self.files.create_file('foo.txt', 'contents')
147
cmdline = f'{self.prefix} {full_path} s3://bucket/key.txt --checksum-algorithm CRC32'
148
self.run_cmd(cmdline, expected_rc=0)
149
self.assertEqual(self.operations_called[0][0].name, 'PutObject')
150
self.assertEqual(self.operations_called[0][1]['ChecksumAlgorithm'], 'CRC32')
151
152
def test_download_with_checksum_mode_crc32(self):
153
self.parsed_responses = [
154
self.head_object_response(),
155
# Mocked GetObject response with a checksum algorithm specified
156
{
157
'ETag': 'foo-1',
158
'ChecksumCRC32': 'checksum',
159
'Body': BytesIO(b'foo')
160
},
161
self.delete_object_response()
162
]
163
cmdline = f'{self.prefix} s3://bucket/foo {self.files.rootdir} --checksum-mode ENABLED'
164
self.run_cmd(cmdline, expected_rc=0)
165
self.assertEqual(self.operations_called[1][0].name, 'GetObject')
166
self.assertEqual(self.operations_called[1][1]['ChecksumMode'], 'ENABLED')
167
168
169
class TestMvCommandWithValidateSameS3Paths(BaseS3TransferCommandTest):
170
171
prefix = 's3 mv '
172
173
def assert_validates_cannot_mv_onto_itself(self, cmd):
174
stderr = self.run_cmd(cmd, expected_rc=255)[1]
175
self.assertIn('Cannot mv a file onto itself', stderr)
176
177
def assert_runs_mv_without_validation(self, cmd):
178
self.parsed_responses = [
179
self.head_object_response(),
180
self.copy_object_response(),
181
self.delete_object_response(),
182
]
183
self.run_cmd(cmd, expected_rc=0)
184
self.assertEqual(len(self.operations_called), 3,
185
self.operations_called)
186
self.assertEqual(self.operations_called[0][0].name, 'HeadObject')
187
self.assertEqual(self.operations_called[1][0].name, 'CopyObject')
188
self.assertEqual(self.operations_called[2][0].name, 'DeleteObject')
189
190
def assert_raises_warning(self, cmd):
191
self.parsed_responses = [
192
self.head_object_response(),
193
self.copy_object_response(),
194
self.delete_object_response(),
195
]
196
stderr = self.run_cmd(cmd, expected_rc=0)[1]
197
self.assertIn('warning: Provided s3 paths may resolve', stderr)
198
199
def test_cant_mv_object_onto_itself_access_point_arn(self):
200
cmdline = (f"{self.prefix}s3://bucket/key "
201
"s3://arn:aws:s3:us-west-2:123456789012:accesspoint/"
202
"myaccesspoint/key "
203
"--validate-same-s3-paths")
204
self.parsed_responses = [
205
{"Bucket": "bucket"}
206
]
207
self.assert_validates_cannot_mv_onto_itself(cmdline)
208
209
def test_cant_mv_object_onto_itself_access_point_arn_as_source(self):
210
cmdline = (f"{self.prefix}s3://arn:aws:s3:us-west-2:123456789012:"
211
"accesspoint/myaccesspoint/key "
212
"s3://bucket/key "
213
"--validate-same-s3-paths")
214
self.parsed_responses = [
215
{"Bucket": "bucket"}
216
]
217
self.assert_validates_cannot_mv_onto_itself(cmdline)
218
219
def test_cant_mv_object_onto_itself_access_point_arn_with_env_var(self):
220
self.environ['AWS_CLI_S3_MV_VALIDATE_SAME_S3_PATHS'] = 'true'
221
cmdline = (f"{self.prefix}s3://bucket/key "
222
"s3://arn:aws:s3:us-west-2:123456789012:accesspoint/"
223
"myaccesspoint/key")
224
self.parsed_responses = [
225
{"Bucket": "bucket"}
226
]
227
self.assert_validates_cannot_mv_onto_itself(cmdline)
228
229
def test_cant_mv_object_onto_itself_access_point_arn_base_key(self):
230
cmdline = (f"{self.prefix}s3://bucket/key "
231
"s3://arn:aws:s3:us-west-2:123456789012:accesspoint/"
232
"myaccesspoint/ "
233
"--validate-same-s3-paths")
234
self.parsed_responses = [
235
{"Bucket": "bucket"}
236
]
237
self.assert_validates_cannot_mv_onto_itself(cmdline)
238
239
def test_cant_mv_object_onto_itself_access_point_arn_base_prefix(self):
240
cmdline = (f"{self.prefix}s3://bucket/prefix/key "
241
"s3://arn:aws:s3:us-west-2:123456789012:accesspoint/"
242
"myaccesspoint/prefix/ "
243
"--validate-same-s3-paths")
244
self.parsed_responses = [
245
{"Bucket": "bucket"}
246
]
247
self.assert_validates_cannot_mv_onto_itself(cmdline)
248
249
def test_cant_mv_object_onto_itself_access_point_alias(self):
250
cmdline = (f"{self.prefix} s3://bucket/key "
251
"s3://myaccesspoint-foobar-s3alias/key "
252
"--validate-same-s3-paths")
253
self.parsed_responses = [
254
{"Account": "123456789012"},
255
{"Bucket": "bucket"}
256
]
257
self.assert_validates_cannot_mv_onto_itself(cmdline)
258
259
def test_cant_mv_object_onto_itself_outpost_access_point_arn(self):
260
cmdline = (f"{self.prefix}s3://bucket/key "
261
"s3://arn:aws:s3-outposts:us-east-1:123456789012:outpost/"
262
"op-foobar/accesspoint/myaccesspoint/key "
263
"--validate-same-s3-paths")
264
self.parsed_responses = [
265
{"Bucket": "bucket"}
266
]
267
self.assert_validates_cannot_mv_onto_itself(cmdline)
268
269
def test_outpost_access_point_alias_raises_error(self):
270
cmdline = (f"{self.prefix} s3://bucket/key "
271
"s3://myaccesspoint-foobar--op-s3/key "
272
"--validate-same-s3-paths")
273
stderr = self.run_cmd(cmdline, expected_rc=255)[1]
274
self.assertIn("Can't resolve underlying bucket name", stderr)
275
276
def test_cant_mv_object_onto_itself_mrap_arn(self):
277
cmdline = (f"{self.prefix} s3://bucket/key "
278
"s3://arn:aws:s3::123456789012:accesspoint/foobar.mrap/key "
279
"--validate-same-s3-paths")
280
self.parsed_responses = [
281
{
282
"AccessPoints": [{
283
"Alias": "foobar.mrap",
284
"Regions": [
285
{"Bucket": "differentbucket"},
286
{"Bucket": "bucket"}
287
]
288
}]
289
}
290
]
291
self.assert_validates_cannot_mv_onto_itself(cmdline)
292
293
def test_get_mrap_buckets_raises_if_alias_not_found(self):
294
cmdline = (f"{self.prefix} s3://bucket/key "
295
"s3://arn:aws:s3::123456789012:accesspoint/foobar.mrap/key "
296
"--validate-same-s3-paths")
297
self.parsed_responses = [
298
{
299
"AccessPoints": [{
300
"Alias": "baz.mrap",
301
"Regions": [
302
{"Bucket": "differentbucket"},
303
{"Bucket": "bucket"}
304
]
305
}]
306
}
307
]
308
stderr = self.run_cmd(cmdline, expected_rc=255)[1]
309
self.assertEqual(
310
"\nCouldn't find multi-region access point with alias foobar.mrap "
311
"in account 123456789012\n",
312
stderr
313
)
314
315
def test_mv_works_if_access_point_arn_resolves_to_different_bucket(self):
316
cmdline = (f"{self.prefix}s3://bucket/key "
317
"s3://arn:aws:s3:us-west-2:123456789012:accesspoint/"
318
"myaccesspoint/key "
319
"--validate-same-s3-paths")
320
self.parsed_responses = [
321
{"Bucket": "differentbucket"},
322
self.head_object_response(),
323
self.copy_object_response(),
324
self.delete_object_response(),
325
]
326
self.run_cmd(cmdline, expected_rc=0)
327
self.assertEqual(len(self.operations_called), 4,
328
self.operations_called)
329
self.assertEqual(self.operations_called[0][0].name, 'GetAccessPoint')
330
self.assertEqual(self.operations_called[1][0].name, 'HeadObject')
331
self.assertEqual(self.operations_called[2][0].name, 'CopyObject')
332
self.assertEqual(self.operations_called[3][0].name, 'DeleteObject')
333
334
def test_mv_works_if_access_point_alias_resolves_to_different_bucket(self):
335
cmdline = (f"{self.prefix} s3://bucket/key "
336
"s3://myaccesspoint-foobar-s3alias/key "
337
"--validate-same-s3-paths")
338
self.parsed_responses = [
339
{"Account": "123456789012"},
340
{"Bucket": "differentbucket"},
341
self.head_object_response(),
342
self.copy_object_response(),
343
self.delete_object_response(),
344
]
345
self.run_cmd(cmdline, expected_rc=0)
346
self.assertEqual(len(self.operations_called), 5,
347
self.operations_called)
348
self.assertEqual(self.operations_called[0][0].name, 'GetCallerIdentity')
349
self.assertEqual(self.operations_called[1][0].name, 'GetAccessPoint')
350
self.assertEqual(self.operations_called[2][0].name, 'HeadObject')
351
self.assertEqual(self.operations_called[3][0].name, 'CopyObject')
352
self.assertEqual(self.operations_called[4][0].name, 'DeleteObject')
353
354
def test_mv_works_if_outpost_access_point_arn_resolves_to_different_bucket(self):
355
cmdline = (f"{self.prefix}s3://bucket/key "
356
"s3://arn:aws:s3-outposts:us-east-1:123456789012:outpost/"
357
"op-foobar/accesspoint/myaccesspoint/key "
358
"--validate-same-s3-paths")
359
self.parsed_responses = [
360
{"Bucket": "differentbucket"},
361
self.head_object_response(),
362
self.copy_object_response(),
363
self.delete_object_response(),
364
]
365
self.run_cmd(cmdline, expected_rc=0)
366
self.assertEqual(len(self.operations_called), 4,
367
self.operations_called)
368
self.assertEqual(self.operations_called[0][0].name, 'GetAccessPoint')
369
self.assertEqual(self.operations_called[1][0].name, 'HeadObject')
370
self.assertEqual(self.operations_called[2][0].name, 'CopyObject')
371
self.assertEqual(self.operations_called[3][0].name, 'DeleteObject')
372
373
@requires_crt
374
def test_mv_works_if_mrap_arn_resolves_to_different_bucket(self):
375
cmdline = (f"{self.prefix} s3://bucket/key "
376
"s3://arn:aws:s3::123456789012:accesspoint/foobar.mrap/key "
377
"--validate-same-s3-paths")
378
self.parsed_responses = [
379
{
380
"AccessPoints": [{
381
"Alias": "foobar.mrap",
382
"Regions": [
383
{"Bucket": "differentbucket"},
384
]
385
}]
386
},
387
self.head_object_response(),
388
self.copy_object_response(),
389
self.delete_object_response(),
390
]
391
self.run_cmd(cmdline, expected_rc=0)
392
self.assertEqual(len(self.operations_called), 4,
393
self.operations_called)
394
self.assertEqual(self.operations_called[0][0].name, 'ListMultiRegionAccessPoints')
395
self.assertEqual(self.operations_called[1][0].name, 'HeadObject')
396
self.assertEqual(self.operations_called[2][0].name, 'CopyObject')
397
self.assertEqual(self.operations_called[3][0].name, 'DeleteObject')
398
399
def test_skips_validation_if_keys_are_different_accesspoint_arn(self):
400
cmdline = (f"{self.prefix}s3://bucket/key "
401
"s3://arn:aws:s3:us-west-2:123456789012:accesspoint/"
402
"myaccesspoint/key2 "
403
"--validate-same-s3-paths")
404
self.assert_runs_mv_without_validation(cmdline)
405
406
def test_skips_validation_if_prefixes_are_different_accesspoint_arn(self):
407
cmdline = (f"{self.prefix}s3://bucket/key "
408
"s3://arn:aws:s3:us-west-2:123456789012:accesspoint/"
409
"myaccesspoint/prefix/ "
410
"--validate-same-s3-paths")
411
self.assert_runs_mv_without_validation(cmdline)
412
413
def test_skips_validation_if_keys_are_different_accesspoint_alias(self):
414
cmdline = (f"{self.prefix} s3://bucket/key "
415
"s3://myaccesspoint-foobar-s3alias/key2 "
416
"--validate-same-s3-paths")
417
self.assert_runs_mv_without_validation(cmdline)
418
419
def test_skips_validation_if_keys_are_different_outpost_arn(self):
420
cmdline = (f"{self.prefix}s3://bucket/key "
421
"s3://arn:aws:s3-outposts:us-east-1:123456789012:outpost/"
422
"op-foobar/accesspoint/myaccesspoint/key2 "
423
"--validate-same-s3-paths")
424
self.assert_runs_mv_without_validation(cmdline)
425
426
def test_skips_validation_if_keys_are_different_outpost_alias(self):
427
cmdline = (f"{self.prefix} s3://bucket/key "
428
"s3://myaccesspoint-foobar--op-s3/key2 "
429
"--validate-same-s3-paths")
430
self.assert_runs_mv_without_validation(cmdline)
431
432
@requires_crt
433
def test_skips_validation_if_keys_are_different_mrap_arn(self):
434
cmdline = (f"{self.prefix} s3://bucket/key "
435
"s3://arn:aws:s3::123456789012:accesspoint/foobar.mrap/key2 "
436
"--validate-same-s3-paths")
437
self.assert_runs_mv_without_validation(cmdline)
438
439
def test_raises_warning_if_validation_not_set(self):
440
cmdline = (f"{self.prefix}s3://bucket/key "
441
"s3://arn:aws:s3:us-west-2:123456789012:accesspoint/"
442
"myaccesspoint/key")
443
self.assert_raises_warning(cmdline)
444
445
def test_raises_warning_if_validation_not_set_source(self):
446
cmdline = (f"{self.prefix}"
447
"s3://arn:aws:s3:us-west-2:123456789012:accesspoint/"
448
"myaccesspoint/key "
449
"s3://bucket/key")
450
self.assert_raises_warning(cmdline)
451
452
453
class TestMvRecursiveCaseConflict(TestSyncCaseConflict):
454
prefix = 's3 mv --recursive '
455
456
@skip_if_case_sensitive()
457
def test_warn_with_existing_file(self):
458
self.files.create_file(self.lower_key, 'mycontent')
459
cmd = (
460
f"{self.prefix} s3://bucket {self.files.rootdir} "
461
"--case-conflict warn"
462
)
463
self.parsed_responses = [
464
self.list_objects_response([self.upper_key]),
465
self.get_object_response(),
466
self.delete_object_response(),
467
]
468
_, stderr, _ = self.run_cmd(cmd, expected_rc=0)
469
assert f"warning: Downloading bucket/{self.upper_key}" in stderr
470
471
def test_warn_with_case_conflicts_in_s3(self):
472
# This test case becomes very flaky because mv
473
# performs a get and delete operation twice.
474
# Delete is called after the get finishes, but
475
# the order of responses become non-deterministic
476
# when downloading multiple objects. The order
477
# could be [get, get, delete, delete] or
478
# [get, delete, get, delete]. Rather than making
479
# complex changes to patch this behavior, we're
480
# delegating the assertions to the sync and cp
481
# test suites.
482
pass
483
484
def test_skip_with_case_conflicts_in_s3(self):
485
cmd = (
486
f"{self.prefix} s3://bucket {self.files.rootdir} "
487
"--case-conflict skip"
488
)
489
self.parsed_responses = [
490
self.list_objects_response([self.upper_key, self.lower_key]),
491
self.get_object_response(),
492
self.delete_object_response(),
493
]
494
_, stderr, _ = self.run_cmd(cmd, expected_rc=0)
495
assert f"warning: Skipping bucket/{self.lower_key}" in stderr
496
497
def test_ignore_with_existing_file(self):
498
self.files.create_file(self.lower_key, 'mycontent')
499
cmd = (
500
f"{self.prefix} s3://bucket {self.files.rootdir} "
501
"--case-conflict ignore"
502
)
503
self.parsed_responses = [
504
self.list_objects_response([self.upper_key]),
505
self.get_object_response(),
506
self.delete_object_response(),
507
]
508
self.run_cmd(cmd, expected_rc=0)
509
510
def test_ignore_with_case_conflicts_in_s3(self):
511
pass
512
513
514
class TestS3ExpressMvRecursive(BaseS3TransferCommandTest):
515
prefix = 's3 mv --recursive '
516
517
def test_s3_express_error_raises_exception(self):
518
cmd = (
519
f"{self.prefix} s3://bucket--usw2-az1--x-s3 {self.files.rootdir} "
520
"--case-conflict error"
521
)
522
_, stderr, _ = self.run_cmd(cmd, expected_rc=255)
523
assert "`error` is not a valid value" in stderr
524
525
def test_s3_express_skip_raises_exception(self):
526
cmd = (
527
f"{self.prefix} s3://bucket--usw2-az1--x-s3 {self.files.rootdir} "
528
"--case-conflict skip"
529
)
530
_, stderr, _ = self.run_cmd(cmd, expected_rc=255)
531
assert "`skip` is not a valid value" in stderr
532
533