in django_airavata/apps/admin/management/commands/archive_user_data.py [0:0]
def handle(self, *args, **options):
try:
# Take --max-age from the command line first, then from the setting
max_age_setting = options['max_age']
if max_age_setting is None:
raise CommandError("Setting GATEWAY_USER_DATA_ARCHIVE_MAX_AGE_DAYS is not configured and --max-age option missing.")
max_age = timezone.now() - datetime.timedelta(days=max_age_setting)
entries_to_archive = self.get_archive_entries(older_than=max_age)
gateway_id = settings.GATEWAY_ID
archive_directory = Path(settings.GATEWAY_USER_DATA_ARCHIVE_DIRECTORY)
archive_directory.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(dir=archive_directory) as tmpdir:
archive_basename = f"archive_{gateway_id}_older_than_{max_age.strftime('%Y-%m-%d-%H-%M-%S')}"
archive_list_filename = f"{archive_basename}.txt"
archive_list_filepath = os.path.join(tmpdir, archive_list_filename)
entry_count = 0
with open(archive_list_filepath, "wt") as archive_list_file:
for entry in entries_to_archive:
entry_count = entry_count + 1
archive_list_file.write(f"{entry.path}\n")
# If nothing matching to archive, just exit
if entry_count == 0:
self.stdout.write(self.style.WARNING("Nothing to archive, exiting now"))
return
# if dry run, just print file and exit
if options['dry_run']:
self.stdout.write(f"DRY RUN: printing {archive_list_filename}, then exiting")
with open(os.path.join(tmpdir, archive_list_filename)) as archive_list_file:
for line in archive_list_file:
self.stdout.write(line)
self.stdout.write(self.style.SUCCESS("DRY RUN: exiting now"))
return
# otherwise, generate a tarball in tmpdir
archive_tarball_filename = f"{archive_basename}.tgz"
archive_tarball_filepath = os.path.join(tmpdir, archive_tarball_filename)
with tarfile.open(archive_tarball_filepath, "w:gz") as tarball:
with open(os.path.join(tmpdir, archive_list_filename)) as archive_list_file:
for line in archive_list_file:
tarball.add(line.strip())
minimum_bytes_size = settings.GATEWAY_USER_DATA_ARCHIVE_MINIMUM_ARCHIVE_SIZE_GB * 1024 ** 3
if os.stat(archive_tarball_filepath).st_size < minimum_bytes_size:
self.stdout.write(self.style.WARNING("Aborting, archive size is not large enough to proceed (size less than GATEWAY_USER_DATA_ARCHIVE_MINIMUM_ARCHIVE_SIZE_GB)"))
# Exit early
return
# Move the archive files into the final destination
shutil.move(archive_list_filepath, archive_directory / archive_list_filename)
shutil.move(archive_tarball_filepath, archive_directory / archive_tarball_filename)
self.stdout.write(self.style.SUCCESS(f"Created tarball: {archive_directory / archive_tarball_filename}"))
# Now we'll remove any files/directories that were in the archive
# and create database records for the archive
try:
# If any error occurs in this block, the transaction will be rolled back
with transaction.atomic():
user_data_archive = models.UserDataArchive(
archive_name=archive_tarball_filename,
archive_path=os.fspath(archive_directory / archive_tarball_filename),
max_modification_time=max_age)
user_data_archive.save()
# delete archived entries
with open(archive_directory / archive_list_filename) as archive_list_file:
for archive_path in archive_list_file:
archive_path = archive_path.strip()
if os.path.isfile(archive_path):
os.remove(archive_path)
elif os.path.isdir(archive_path):
shutil.rmtree(archive_path)
else:
self.stdout.write(self.style.WARNING(f"Cannot delete {archive_path} as it is neither a file nor directory, perhaps was already removed"))
archive_entry = models.UserDataArchiveEntry(user_data_archive=user_data_archive, entry_path=archive_path)
archive_entry.save()
except Exception as e:
self.stdout.write(self.style.ERROR("Failed while deleting archived data, attempting to roll back"))
with tarfile.open(archive_directory / archive_tarball_filename) as tf:
tf.extractall(path="/")
logger.exception(f"[archive_user_data] Failed to delete archived files, but unarchived from tarball {archive_directory / archive_tarball_filename}", exc_info=e)
raise CommandError(f"Failed to delete archived files, but unarchived from tarball {archive_directory / archive_tarball_filename}") from e
self.stdout.write(self.style.SUCCESS("Successfully removed archived user data"))
except CommandError:
raise
except Exception:
logger.exception("[archive_user_data] Failed to create user data archive")