Skip to content

Commit cf2392e

Browse files
author
Rakshil Modi
committed
Handling versioning of object
Updated comments
1 parent 3172735 commit cf2392e

File tree

9 files changed

+950
-6
lines changed

9 files changed

+950
-6
lines changed

awscli/customizations/s3/filegenerator.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@
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,
26+
BucketVersionLister,
2527
create_warning,
2628
find_bucket_key,
2729
find_dest_path_comp_key,
2830
get_file_stat,
31+
split_s3_bucket_key,
2932
)
3033

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

awscli/customizations/s3/fileinfo.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,40 @@ 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+
:param is_delete_marker: Whether this version is a delete marker.
124+
:type is_delete_marker: boolean
125+
"""
126+
super().__init__(**kwargs)
127+
self.version_id = version_id
128+
self.is_delete_marker = is_delete_marker
129+
130+
def is_glacier_compatible(self):
131+
"""
132+
Determines glacier compatibility for versioned S3 objects, with special handling for delete operations.
133+
134+
This method overrides the parent FileInfo.is_glacier_compatible() to provide enhanced
135+
compatibility checking for S3 objects stored in glacier storage classes
136+
when versioning is enabled on the bucket.
137+
138+
This method override allows delete operations to proceed on versioned glacier objects.
139+
Since delete operations on glacier objects succeed regardless of storage class
140+
141+
:rtype: bool
142+
:returns: True if the operation can proceed on glacier objects (specifically for delete
143+
operations on versioned objects), False if the operation would fail due to
144+
glacier storage class restrictions
145+
"""
146+
if self.operation_name == 'delete':
147+
return True
148+
return super().is_glacier_compatible()

0 commit comments

Comments
 (0)