|
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,25 +1778,70 @@ 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 |
| - # Delete the buckets if they exists |
| 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')
|
| 1830 | + |
| 1831 | + # This is here in case there is a mistake and the S3 buckets are not deleted |
| 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": |
| 1834 | + for bucket in s3_buckets: |
| 1835 | + if bucket.startswith(bucket_name): |
| 1836 | + try: |
| 1837 | + self.empty_bucket(bucket_name) |
| 1838 | + self.s3_client.delete_bucket(Bucket=bucket) |
| 1839 | + except Exception as e: |
| 1840 | + print(f'Error: {e}') |
| 1841 | + continue |
| 1842 | + log(f'\nBucket {bucket} deleted\n') |
1796 | 1843 | else:
|
1797 |
| - log(f'Bucket {bucket_name} not found\n') |
| 1844 | + log(f'\nBucket {bucket_name} not found\n') |
1798 | 1845 |
|
1799 | 1846 | return True
|
1800 | 1847 |
|
@@ -1862,7 +1909,7 @@ def get_regions(self):
|
1862 | 1909 | print_error()
|
1863 | 1910 | sys.exit(1)
|
1864 | 1911 |
|
1865 |
| - def list_objects_in_bucket(self, bucket_name): |
| 1912 | + def get_objects(self, bucket_name): |
1866 | 1913 | '''List all the objects in the given bucket'''
|
1867 | 1914 |
|
1868 | 1915 | if not bucket_name:
|
@@ -3686,7 +3733,7 @@ def _index_locally(self, folder):
|
3686 | 3733 |
|
3687 | 3734 | log(f'Total folders processed: {len(rows)}')
|
3688 | 3735 |
|
3689 |
| - log(f'INDEXING SUCCESSFULLY COMPLETED') |
| 3736 | + log(f'\nINDEXING SUCCESSFULLY COMPLETED') |
3690 | 3737 |
|
3691 | 3738 | lastagedbytes = 0
|
3692 | 3739 | for i in range(0, len(daysaged)):
|
@@ -5489,7 +5536,7 @@ class TextualStringListSelector(App[list]):
|
5489 | 5536 |
|
5490 | 5537 | BINDINGS = [("q", "request_quit", "Quit")]
|
5491 | 5538 |
|
5492 |
| - def __init__(self, title: str, items: list[str]): |
| 5539 | + def __init__(self, title, items): |
5493 | 5540 | super().__init__()
|
5494 | 5541 | self.title = title
|
5495 | 5542 | self.items = items
|
@@ -5520,7 +5567,7 @@ class TableArchive(App[list]):
|
5520 | 5567 |
|
5521 | 5568 | BINDINGS = [("q", "request_quit", "Quit")]
|
5522 | 5569 |
|
5523 |
| - def __init__(self, files: list[str]): |
| 5570 | + def __init__(self, files): |
5524 | 5571 | super().__init__()
|
5525 | 5572 | self.files = files
|
5526 | 5573 |
|
@@ -6866,6 +6913,104 @@ def subcmd_umount(self, arch: Archiver):
|
6866 | 6913 | print_error()
|
6867 | 6914 | return False
|
6868 | 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 | + |
6869 | 7014 | def subcmd_ssh(self, cfg: ConfigManager, aws: AWSBoto):
|
6870 | 7015 | '''SSH into an AWS EC2 instance'''
|
6871 | 7016 |
|
@@ -7269,6 +7414,13 @@ def parse_arguments(self):
|
7269 | 7414 | parser_update.add_argument('--rclone', '-r', dest='rclone', action='store_true',
|
7270 | 7415 | help="Update rclone to latests version")
|
7271 | 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 | + |
7272 | 7424 | return parser
|
7273 | 7425 |
|
7274 | 7426 |
|
@@ -7499,6 +7651,8 @@ def main():
|
7499 | 7651 | # Calling check_update as we are checking for udpates (used to store the last timestamp check)
|
7500 | 7652 | cfg.check_update()
|
7501 | 7653 | res = cmd.subcmd_update(mute_no_update=False)
|
| 7654 | + elif args.subcmd in ['test', 'tst']: |
| 7655 | + res = cmd.subcmd_test(cfg, arch, aws) |
7502 | 7656 | else:
|
7503 | 7657 |
|
7504 | 7658 | # Check credentials
|
|
0 commit comments