Skip to content

Feature/cleanup script - #5

Open
SeanBerrieHRI wants to merge 17 commits into
mainfrom
feature/cleanup-script
Open

Feature/cleanup script#5
SeanBerrieHRI wants to merge 17 commits into
mainfrom
feature/cleanup-script

Conversation

@SeanBerrieHRI

Copy link
Copy Markdown

Summary

Adds a folder_cleanup.py script for removing old files/folders from a provided path (e.g. cache, temp, deleted) based on a retention period provided.

  • Recursively walks a target directory (--dir_path) and removes files whose last-modified time exceeds --retention_days (default: 90)
  • After clearing old files, removes any directories left empty as a result, including the target directory itself if it ends up empty
  • Handles Windows/OneDrive read-only file attributes that would otherwise cause EACCES errorsthe attribute and retrying
  • Non-readonly removal errors (e.g. ACL delete-deny) are re-raised

Test plan

  • Unit tests for handle_path, handle_directory, handle_file_removal, and readonly-file/directory removal (tests/test_folder_cleanup.py)

@servicedesk-health-ri

servicedesk-health-ri commented Jul 17, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Comment on lines +6 to +20
def cleanup(dir_path, retention_days):
if not os.path.exists(dir_path):
print(f"Path does not exist: {dir_path}")
return

if not os.path.isdir(dir_path):
print(f"Path is not a directory: {dir_path}")
return

for entry in os.listdir(dir_path):
entry_path = os.path.join(dir_path, entry)
handle_path(entry_path, retention_days)

if not os.listdir(dir_path):
handle_directory_removal(dir_path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not check for symlinks (on the entry level). Does the caller know or can be trusted with these things?

Comment on lines +38 to +40
for entry in contents:
entry_path = os.path.join(directory, entry)
handle_path(entry_path, retention_days)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This recurses into other directories, but does not check for symlinks. A mistake could therefore delete folders outside of the cache dirs, making it possible to (depending on the permissions the user has) delete stuff that is system critical. This could throw an error when a symlink is encountered (os.path.islink(entry_path) if claude is to be believed).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cleanup function itself is not covered by tests, only the helper functions.

Comment on lines +72 to +81
if func in (os.rmdir, os.remove, os.unlink) and excvalue.errno == errno.EACCES:
# Windows marks some OneDrive files read-only, which makes rmdir/remove raise
# "access denied" (EACCES). Clearing the attribute and retrying fixes that case.
os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777
func(path)
else:
# Not a read-only issue (e.g. an ACL delete-deny) - re-raise the original exception.
# Works because rmtree calls onerror() from inside its own except block, so the
# exception context is still live for a bare `raise` to pick up.
raise

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation levels drop back to 2 spaces instead of 4 for the rest of the file.

Comment on lines +48 to +49
if last_modified_days <= retention_days:
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a use-case for debug logging output on these checks?

Comment on lines +87 to +90
if func in (os.rmdir, os.remove, os.unlink) and excvalue.errno == errno.EACCES:
# Windows marks some OneDrive files read-only, which makes rmdir/remove raise
# "access denied" (EACCES). Clearing the attribute and retrying fixes that case.
os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sonar's comment is valid, and I wonder if we need this type of feature to cater for windows use? @agjharms your opinion on this?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know, neither at Health-RI, nor at any UMC, XNAT is run on a Windows environment and the kind of folders that this cleanup script is meant for will not be placed on a OneDrive folder. I appreciate the inclusion of this environment but in my opinion we can drop the support for this.

Comment on lines +59 to +62

try:
os.remove(file_path)
print(f"Removed file {file_path} - last modified {last_modified_days} days")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sonar's comment is reasonable, to a certain extend. If this is only ever ran by a human who knows what they are doing this is an acceptable risk. Still we could add extra checks for whether the user under which the script runs has the right permissions, and if so check whether it's not a system folder?

@agjharms agjharms Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script will only be run, or set up to be run, by administrators of XNAT deployments. I don't see any reason why this script would be run by an LLM, so we don't need to secure ourselves against that scenario.

@sonarqubecloud

sonarqubecloud Bot commented Jul 23, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants