@@ -316,3 +316,98 @@ def uploadToS3(
316316 except Exception as e :
317317 logger .error (f"Unexpected error uploading to S3: { e } " )
318318 return False
319+
320+
321+ def downloadFromS3 (
322+ bucket_name : str ,
323+ object_name : str ,
324+ local_dir : str ,
325+ endpoint_url = None ,
326+ aws_access_key_id = None ,
327+ aws_secret_access_key = None ,
328+ region_name = None
329+ ) -> bool :
330+ """
331+ Download a tar.gz file from S3-compatible storage to a backup directory.
332+
333+ Args:
334+ bucket_name: Name of the S3 bucket
335+ object_name: S3 object name to download
336+ local_dir: Directory path where the file will be downloaded
337+ endpoint_url: S3-compatible endpoint URL (e.g., for MinIO, Ceph)
338+ aws_access_key_id: AWS access key ID (if not using environment variables)
339+ aws_secret_access_key: AWS secret access key (if not using environment variables)
340+ region_name: AWS region name (default: us-east-1)
341+
342+ Returns:
343+ bool: True if file was downloaded successfully, False otherwise
344+ """
345+ # Validate backup directory
346+ if not os .path .exists (local_dir ):
347+ logger .info (f"Backup directory does not exist, creating: { local_dir } " )
348+ try :
349+ os .makedirs (local_dir , exist_ok = True )
350+ except Exception as e :
351+ logger .error (f"Failed to create backup directory { local_dir } : { e } " )
352+ return False
353+
354+ # Construct the full file path
355+ file_path = os .path .join (local_dir , object_name )
356+
357+ # Warn if file doesn't have .tar.gz extension
358+ if not object_name .endswith ('.tar.gz' ):
359+ logger .warning (f"Object does not have .tar.gz extension: { object_name } " )
360+
361+ # Configure S3 client
362+ try :
363+ s3_config = {}
364+
365+ if endpoint_url :
366+ s3_config ['endpoint_url' ] = endpoint_url
367+ if aws_access_key_id and aws_secret_access_key :
368+ s3_config ['aws_access_key_id' ] = aws_access_key_id
369+ s3_config ['aws_secret_access_key' ] = aws_secret_access_key
370+ if region_name :
371+ s3_config ['region_name' ] = region_name
372+ else :
373+ s3_config ['region_name' ] = 'us-east-1'
374+
375+ s3_client = boto3 .client ('s3' , ** s3_config )
376+
377+ # Check if object exists and get its size
378+ logger .info (f"Downloading s3://{ bucket_name } /{ object_name } to { file_path } " )
379+
380+ try :
381+ response = s3_client .head_object (Bucket = bucket_name , Key = object_name )
382+ file_size = response .get ('ContentLength' , 0 )
383+ logger .info (f"Object size: { file_size / (1024 * 1024 ):.2f} MB" )
384+ except ClientError as e :
385+ if e .response .get ('Error' , {}).get ('Code' ) == '404' :
386+ logger .error (f"Object not found in S3: s3://{ bucket_name } /{ object_name } " )
387+ return False
388+ raise
389+
390+ # Download the file
391+ s3_client .download_file (bucket_name , object_name , file_path )
392+
393+ # Verify the downloaded file exists
394+ if os .path .exists (file_path ):
395+ downloaded_size = os .path .getsize (file_path )
396+ logger .info (f"Successfully downloaded { object_name } to { file_path } " )
397+ logger .info (f"Downloaded file size: { downloaded_size / (1024 * 1024 ):.2f} MB" )
398+ return True
399+ else :
400+ logger .error (f"Download completed but file not found at { file_path } " )
401+ return False
402+
403+ except NoCredentialsError :
404+ logger .error ("AWS credentials not found. Please provide credentials or configure environment variables." )
405+ return False
406+ except ClientError as e :
407+ error_code = e .response .get ('Error' , {}).get ('Code' , 'Unknown' )
408+ error_message = e .response .get ('Error' , {}).get ('Message' , str (e ))
409+ logger .error (f"S3 client error ({ error_code } ): { error_message } " )
410+ return False
411+ except Exception as e :
412+ logger .error (f"Unexpected error downloading from S3: { e } " )
413+ return False
0 commit comments