Skip to content

Commit 4fb3893

Browse files
author
Rakshil Modi
committed
Handling versioning of object
Updated comments Remove BucketLister class Added change logs Modifying S3 Handler Format display message Changing names of the class changing to marked down
1 parent c6d4a97 commit 4fb3893

File tree

10 files changed

+923
-6
lines changed

10 files changed

+923
-6
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "enhancement",
3+
"category": "`s3`",
4+
"description": "Add all-versions flag with AWS CLI S3 rm command to delete all versions of objects present in a versioned-enabled bucket. fixes `#4070 <https://github.com/aws/aws-cli/issues/4070>`__"
5+
}

awscli/customizations/s3/filegenerator.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919
from dateutil.tz import tzlocal
2020

2121
from awscli.compat import queue
22+
from awscli.customizations.s3.fileinfo import VersionedFileInfo
2223
from awscli.customizations.s3.utils import (
2324
EPOCH_TIME,
2425
BucketLister,
2526
create_warning,
2627
find_bucket_key,
2728
find_dest_path_comp_key,
2829
get_file_stat,
30+
split_s3_bucket_key,
2931
)
3032

3133
_open = open
@@ -406,3 +408,109 @@ def _list_single_object(self, s3_path):
406408
response['LastModified'] = last_update.astimezone(tzlocal())
407409
response['ETag'] = response.pop('ETag', None)
408410
return s3_path, response
411+
412+
413+
class VersionedFileGenerator:
414+
"""
415+
This class generates VersionedFileInfo objects for all versions of objects in a bucket.
416+
It uses the BucketLister class to list all versions and creates appropriate
417+
VersionedFileInfo objects for each version.
418+
"""
419+
420+
def __init__(
421+
self,
422+
client,
423+
operation_name,
424+
follow_symlinks=True,
425+
page_size=None,
426+
result_queue=None,
427+
request_parameters=None,
428+
):
429+
"""
430+
Initialize a new VersionedFileGenerator.
431+
432+
:param client: The S3 client to use.
433+
:param operation_name: The name of the operation to perform.
434+
:param follow_symlinks: Whether to follow symlinks.
435+
:param page_size: The number of items to include in each API response.
436+
:param result_queue: Queue for results and warnings.
437+
:param request_parameters: Additional parameters for the request.
438+
"""
439+
self._client = client
440+
self.operation_name = operation_name
441+
self.follow_symlinks = follow_symlinks
442+
self.page_size = page_size
443+
self.result_queue = result_queue
444+
if not result_queue:
445+
self.result_queue = queue.Queue()
446+
self.request_parameters = {}
447+
if request_parameters is not None:
448+
self.request_parameters = request_parameters
449+
self._version_lister = BucketLister(client)
450+
451+
def call(self, files):
452+
"""
453+
Generate VersionedFileInfo objects for all versions of objects.
454+
455+
:param files: Dictionary containing source and destination information.
456+
:yields: VersionedFileInfo objects for each version of each object.
457+
"""
458+
source = files['src']['path']
459+
src_type = files['src']['type']
460+
dest_type = files['dest']['type']
461+
462+
# Use the list_object_versions method to get all versions
463+
file_iterator = self.list_object_versions(source, files['dir_op'])
464+
465+
for src_path, content, version_id in file_iterator:
466+
dest_path, compare_key = find_dest_path_comp_key(files, src_path)
467+
468+
# Create a VersionedFileInfo for this object version
469+
yield VersionedFileInfo(
470+
src=src_path,
471+
dest=dest_path,
472+
compare_key=compare_key,
473+
size=content.get('Size', 0),
474+
last_update=content.get('LastModified'),
475+
src_type=src_type,
476+
dest_type=dest_type,
477+
operation_name=self.operation_name,
478+
associated_response_data=content,
479+
version_id=version_id,
480+
)
481+
482+
def list_object_versions(self, s3_path, dir_op):
483+
"""
484+
This function yields the appropriate object versions or all object versions
485+
under a common prefix depending if the operation is on objects under a
486+
common prefix. It yields the file's source path, content, and version ID.
487+
488+
:param s3_path: The S3 path to list versions for.
489+
:param dir_op: Whether this is a directory operation.
490+
:yields: Tuples of (source_path, content, version_id)
491+
"""
492+
bucket, key = split_s3_bucket_key(s3_path)
493+
494+
# Short circuit path: if we are not recursing into the s3
495+
# bucket and a specific path was given, we can just yield
496+
# that path and not have to call any operation in s3.
497+
# However, for versioned objects, we still need to list all versions
498+
# even for a specific object, so we don't have a short circuit path here.
499+
500+
# List all versions of objects
501+
for (
502+
src_path,
503+
content,
504+
version_id,
505+
) in self._version_lister.list_object_versions(
506+
bucket=bucket,
507+
prefix=key,
508+
page_size=self.page_size,
509+
extra_args=self.request_parameters.get('ListObjectVersions', {}),
510+
):
511+
# If this is not a directory operation and the path doesn't match exactly,
512+
# skip it (similar to the behavior in FileGenerator.list_objects)
513+
if not dir_op and s3_path != src_path:
514+
continue
515+
516+
yield src_path, content, version_id

awscli/customizations/s3/fileinfo.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,37 @@ def _is_restored(self, response_data):
109109
# restored back to S3.
110110
# 'Restore' looks like: 'ongoing-request="false", expiry-date="..."'
111111
return 'ongoing-request="false"' in response_data.get('Restore', '')
112+
113+
114+
class VersionedFileInfo(FileInfo):
115+
def __init__(self, version_id=None, is_delete_marker=False, **kwargs):
116+
"""
117+
This class extends FileInfo to include version information for S3 objects.
118+
It is specifically designed for operations that need to work with versioned
119+
S3 objects, such as deleting all versions of an object.
120+
121+
:param version_id: The version ID of the S3 object.
122+
:type version_id: string
123+
"""
124+
super().__init__(**kwargs)
125+
self.version_id = version_id
126+
127+
def is_glacier_compatible(self):
128+
"""
129+
Determines glacier compatibility for versioned S3 objects, with special handling for delete operations.
130+
131+
This method overrides the parent FileInfo.is_glacier_compatible() to provide enhanced
132+
compatibility checking for S3 objects stored in glacier storage classes
133+
when versioning is enabled on the bucket.
134+
135+
This method override allows delete operations to proceed on versioned glacier objects.
136+
Since delete operations on glacier objects succeed regardless of storage class
137+
138+
:rtype: bool
139+
:returns: True if the operation can proceed on glacier objects (specifically for delete
140+
operations on versioned objects), False if the operation would fail due to
141+
glacier storage class restrictions
142+
"""
143+
if self.operation_name == 'delete':
144+
return True
145+
return super().is_glacier_compatible()

0 commit comments

Comments
 (0)