|
6 | 6 | """
|
7 | 7 |
|
8 | 8 | # internal modules
|
| 9 | +import random |
| 10 | +import string |
9 | 11 | from textual.widgets import DataTable, Footer, Button
|
10 | 12 | from textual.widgets import Label, Input, LoadingIndicator
|
11 | 13 | from textual.screen import ModalScreen
|
@@ -1776,30 +1778,63 @@ def create_bucket(self, bucket_name, region):
|
1776 | 1778 | print_error()
|
1777 | 1779 | return False
|
1778 | 1780 |
|
1779 |
| - def delete_bucket(self, bucket_name): |
1780 |
| - '''Delete the given bucket''' |
| 1781 | + def empty_bucket(self, bucket_name): |
1781 | 1782 |
|
| 1783 | + if not bucket_name: |
| 1784 | + raise ValueError('No bucket name provided') |
| 1785 | + |
| 1786 | + # Added a check to prevent accidental deletion of buckets |
1782 | 1787 | if os.environ.get('DEBUG') != '1':
|
1783 |
| - raise ValueError('Buckets cannot be deleted outside DEBUG mode.') |
| 1788 | + raise ValueError('Objects in bucket cannot be deleted outside DEBUG mode.') |
| 1789 | + |
| 1790 | + # Additional check to prevent accidental deletion of buckets |
| 1791 | + if not bucket_name.startswith('froster-unittest') and not bucket_name.startswith('froster-cli-test'): |
| 1792 | + raise ValueError('Bucket name must start with "froster-unittest" or "froster-cli-test" to be emptied.') |
| 1793 | + |
| 1794 | + try: |
| 1795 | + paginator = self.s3_client.get_paginator('list_objects_v2') |
| 1796 | + for page in paginator.paginate(Bucket=bucket_name): |
| 1797 | + for obj in page.get('Contents', []): |
| 1798 | + # print(f"Deleting {obj['Key']}") |
| 1799 | + self.s3_client.delete_object(Bucket=bucket_name, Key=obj['Key']) |
| 1800 | + |
| 1801 | + return True |
| 1802 | + |
| 1803 | + except Exception: |
| 1804 | + print_error() |
| 1805 | + return False |
| 1806 | + |
| 1807 | + def delete_bucket(self, bucket_name): |
| 1808 | + '''Delete the given bucket''' |
1784 | 1809 |
|
1785 | 1810 | if not bucket_name:
|
1786 | 1811 | raise ValueError('No bucket name provided')
|
| 1812 | + |
| 1813 | + # Added a check to prevent accidental deletion of buckets |
| 1814 | + if os.environ.get('DEBUG') != '1': |
| 1815 | + raise ValueError('Objects in bucket cannot be deleted outside DEBUG mode.') |
1787 | 1816 |
|
| 1817 | + # Additional check to prevent accidental deletion of buckets |
| 1818 | + if not bucket_name.startswith('froster-unittest') and not bucket_name.startswith('froster-cli-test'): |
| 1819 | + raise ValueError('Bucket name must start with "froster-unittest" or "froster-cli-test" to be deleted.') |
| 1820 | + |
1788 | 1821 | try:
|
1789 | 1822 |
|
1790 | 1823 | s3_buckets = self.get_buckets()
|
1791 | 1824 |
|
1792 | 1825 | # Delete the bucket if its exists
|
1793 | 1826 | if bucket_name in s3_buckets:
|
| 1827 | + self.empty_bucket(bucket_name) |
1794 | 1828 | self.s3_client.delete_bucket(Bucket=bucket_name)
|
1795 | 1829 | log(f'Bucket {bucket_name} deleted\n')
|
1796 | 1830 |
|
1797 | 1831 | # This is here in case there is a mistake and the S3 buckets are not deleted
|
1798 |
| - # This will erase all the froster-unittest* buckets at once |
1799 |
| - elif bucket_name == "froster-unittest": |
| 1832 | + # This will erase all the froster-unittest* or froster-cli-test buckets at once |
| 1833 | + elif bucket_name == "froster-unittest" or bucket_name == "froster-cli-test": |
1800 | 1834 | for bucket in s3_buckets:
|
1801 | 1835 | if bucket.startswith(bucket_name):
|
1802 | 1836 | try:
|
| 1837 | + self.empty_bucket(bucket_name) |
1803 | 1838 | self.s3_client.delete_bucket(Bucket=bucket)
|
1804 | 1839 | except Exception as e:
|
1805 | 1840 | print(f'Error: {e}')
|
@@ -1874,7 +1909,7 @@ def get_regions(self):
|
1874 | 1909 | print_error()
|
1875 | 1910 | sys.exit(1)
|
1876 | 1911 |
|
1877 |
| - def list_objects_in_bucket(self, bucket_name): |
| 1912 | + def get_objects(self, bucket_name): |
1878 | 1913 | '''List all the objects in the given bucket'''
|
1879 | 1914 |
|
1880 | 1915 | if not bucket_name:
|
@@ -6878,6 +6913,104 @@ def subcmd_umount(self, arch: Archiver):
|
6878 | 6913 | print_error()
|
6879 | 6914 | return False
|
6880 | 6915 |
|
| 6916 | + def subcmd_test(self, cfg: ConfigManager, arch: Archiver, aws: AWSBoto): |
| 6917 | + '''Test basic functionality of Froster''' |
| 6918 | + |
| 6919 | + try: |
| 6920 | + def tearDown(bucket_name, folder_path): |
| 6921 | + # Delete the directory |
| 6922 | + shutil.rmtree(folder_path) |
| 6923 | + |
| 6924 | + # Delete the bucket |
| 6925 | + os.environ['DEBUG'] = '1' |
| 6926 | + aws.delete_bucket(bucket_name) |
| 6927 | + |
| 6928 | + log('\nTESTING FROSTER...') |
| 6929 | + |
| 6930 | + if not aws.check_credentials(prints=True): |
| 6931 | + log('\nTESTING FAILED\n') |
| 6932 | + return False |
| 6933 | + |
| 6934 | + # Generate a random bucket name |
| 6935 | + rnd = ''.join(random.choices( |
| 6936 | + string.ascii_lowercase + string.digits, k=4)) |
| 6937 | + new_bucket_name = f'froster-cli-test-{rnd}' |
| 6938 | + |
| 6939 | + # Set temporary this new bucket name in the configuration |
| 6940 | + cfg.bucket_name = new_bucket_name |
| 6941 | + |
| 6942 | + # Create a dummy file |
| 6943 | + folder_path = tempfile.mkdtemp(prefix='froster_test_') |
| 6944 | + file_path = os.path.join(folder_path, 'dummy_file') |
| 6945 | + |
| 6946 | + log(f'\nCreating dummy file {file_path}...') |
| 6947 | + |
| 6948 | + with open(file_path, 'wb') as f: |
| 6949 | + f.truncate(1) |
| 6950 | + |
| 6951 | + subprocess.run(['touch', file_path]) |
| 6952 | + |
| 6953 | + log(f' ....dummy file create') |
| 6954 | + |
| 6955 | + # Create a new bucket |
| 6956 | + if not aws.create_bucket(bucket_name=new_bucket_name, region=cfg.region): |
| 6957 | + tearDown(new_bucket_name, folder_path) |
| 6958 | + log('\nTESTING FAILED\n') |
| 6959 | + return False |
| 6960 | + |
| 6961 | + # Mocking the index arguments |
| 6962 | + self.args.folders = [folder_path] |
| 6963 | + self.args.permissions = False |
| 6964 | + self.args.pwalkcopy = None |
| 6965 | + |
| 6966 | + # Running index command |
| 6967 | + if not self.subcmd_index(cfg, arch): |
| 6968 | + tearDown(new_bucket_name, folder_path) |
| 6969 | + log('\nTESTING FAILED\n') |
| 6970 | + return False |
| 6971 | + |
| 6972 | + # Mocking the archive arguments |
| 6973 | + self.args.older = 0 |
| 6974 | + self.args.newer = 0 |
| 6975 | + self.args.reset = False |
| 6976 | + self.args.recursive = False |
| 6977 | + self.args.nih = False |
| 6978 | + self.args.nihref = None |
| 6979 | + self.args.notar = True # True to check by file name in the archive |
| 6980 | + self.args.force = False |
| 6981 | + self.args.noslurm = True # Avoid slurm execution |
| 6982 | + |
| 6983 | + # Running archive command |
| 6984 | + if not self.subcmd_archive(arch, aws): |
| 6985 | + tearDown(new_bucket_name, folder_path) |
| 6986 | + log('\nTESTING FAILED\n') |
| 6987 | + return False |
| 6988 | + |
| 6989 | + # Mocking the delete command |
| 6990 | + self.args.bucket = None |
| 6991 | + self.args.debug = False |
| 6992 | + self.args.recursive = False |
| 6993 | + self.args.noslurm = True # Avoid slurm execution |
| 6994 | + |
| 6995 | + # Running delete command |
| 6996 | + if not self.subcmd_delete(arch, aws): |
| 6997 | + tearDown(new_bucket_name, folder_path) |
| 6998 | + log('\nTESTING FAILED\n') |
| 6999 | + return False |
| 7000 | + |
| 7001 | + # Clean up |
| 7002 | + tearDown(new_bucket_name, folder_path) |
| 7003 | + |
| 7004 | + log('\nTEST SUCCESSFULLY COMPLETED\n') |
| 7005 | + |
| 7006 | + return True |
| 7007 | + |
| 7008 | + except Exception: |
| 7009 | + print_error() |
| 7010 | + aws.delete_bucket(new_bucket_name) |
| 7011 | + log('\nTESTING FAILED\n') |
| 7012 | + return False |
| 7013 | + |
6881 | 7014 | def subcmd_ssh(self, cfg: ConfigManager, aws: AWSBoto):
|
6882 | 7015 | '''SSH into an AWS EC2 instance'''
|
6883 | 7016 |
|
@@ -7281,6 +7414,13 @@ def parse_arguments(self):
|
7281 | 7414 | parser_update.add_argument('--rclone', '-r', dest='rclone', action='store_true',
|
7282 | 7415 | help="Update rclone to latests version")
|
7283 | 7416 |
|
| 7417 | + # *** |
| 7418 | + |
| 7419 | + parser_test = subparsers.add_parser('test', aliases=['tst'], |
| 7420 | + description=textwrap.dedent(f''' |
| 7421 | + Test basic functionality of Froster |
| 7422 | + '''), formatter_class=argparse.RawTextHelpFormatter) |
| 7423 | + |
7284 | 7424 | return parser
|
7285 | 7425 |
|
7286 | 7426 |
|
@@ -7511,6 +7651,8 @@ def main():
|
7511 | 7651 | # Calling check_update as we are checking for udpates (used to store the last timestamp check)
|
7512 | 7652 | cfg.check_update()
|
7513 | 7653 | res = cmd.subcmd_update(mute_no_update=False)
|
| 7654 | + elif args.subcmd in ['test', 'tst']: |
| 7655 | + res = cmd.subcmd_test(cfg, arch, aws) |
7514 | 7656 | else:
|
7515 | 7657 |
|
7516 | 7658 | # Check credentials
|
|
0 commit comments