Repository: ietf-tools/datatracker

Issue:
post_submission makes poor assumptions about file systems
### Describe the issue

While working on #6185 and #6186, I tried setting `IDSUBMIT_REPOSITORY_PATH` to a path on a different filesystem from `IDSUBMIT_STAGING_PATH`. This caused a failure when posting a submission:
```
Traceback (most recent call last):
  File "/home/dev/.local/lib/python3.9/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/home/dev/.local/lib/python3.9/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/workspace/ietf/submit/views.py", line 515, in submission_status
    post_submission(request, submission, desc, desc)
  File "/usr/local/lib/python3.9/contextlib.py", line 79, in inner
    return func(*args, **kwds)
  File "/workspace/ietf/submit/utils.py", line 451, in post_submission
    move_files_to_repository(submission)
  File "/workspace/ietf/submit/utils.py", line 654, in move_files_to_repository
    os.rename(source, dest)

Exception Type: OSError at /submit/status/136279/c82dbc3f1b66260b7762926c58beb6a7/
Exception Value: [Errno 18] Invalid cross-device link: 'test/staging/draft-fluhrer-cfrg-ntru-03.txt' -> '/assets/ietf-ftp/internet-drafts/draft-fluhrer-cfrg-ntru-03.txt'
```
The call to `os.rename()` assumes that the source and dest are on the same filesystem. This is unwarranted.

This will certainly need to be fixed (or avoided) when refactoring the artifact storage.

### Code of Conduct

- [X] I agree to follow the [IETF's Code of Conduct](https://github.com/ietf-tools/.github/blob/main/CODE_OF_CONDUCT.md)


Discussion:

Resolving patch:
diff --git a/ietf/submit/utils.py b/ietf/submit/utils.py
--- a/ietf/submit/utils.py
+++ b/ietf/submit/utils.py
@@ -11,6 +11,8 @@
 import traceback
 import xml2rfc
 
+from pathlib import Path
+from shutil import move
 from typing import Optional, Union  # pyflakes:ignore
 from unidecode import unidecode
 
@@ -639,24 +641,27 @@ def cancel_submission(submission):
     submission.save()
     remove_submission_files(submission)
 
+
 def rename_submission_files(submission, prev_rev, new_rev):
     for ext in settings.IDSUBMIT_FILE_TYPES:
-        source = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.%s' % (submission.name, prev_rev, ext))
-        dest = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.%s' % (submission.name, new_rev, ext))
-        if os.path.exists(source):
-            os.rename(source, dest)
+        staging_path = Path(settings.IDSUBMIT_STAGING_PATH) 
+        source = staging_path / f"{submission.name}-{prev_rev}.{ext}"
+        dest = staging_path / f"{submission.name}-{new_rev}.{ext}"
+        if source.exists():
+            move(source, dest)
+
 
 def move_files_to_repository(submission):
     for ext in settings.IDSUBMIT_FILE_TYPES:
-        source = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.%s' % (submission.name, submission.rev, ext))
-        dest = os.path.join(settings.IDSUBMIT_REPOSITORY_PATH, '%s-%s.%s' % (submission.name, submission.rev, ext))
-        if os.path.exists(source):
-            os.rename(source, dest)
-        else:
-            if os.path.exists(dest):
-                log.log("Intended to move '%s' to '%s', but found source missing while destination exists.")
-            elif ext in submission.file_types.split(','):
-                raise ValueError("Intended to move '%s' to '%s', but found source and destination missing.")
+        fname = f"{submission.name}-{submission.rev}.{ext}"
+        source = Path(settings.IDSUBMIT_STAGING_PATH) / fname
+        dest = Path(settings.IDSUBMIT_REPOSITORY_PATH) / fname
+        if source.exists():
+            move(source, dest)
+        elif dest.exists():
+            log.log("Intended to move '%s' to '%s', but found source missing while destination exists.")
+        elif ext in submission.file_types.split(','):
+            raise ValueError("Intended to move '%s' to '%s', but found source and destination missing.")
 
 
 def remove_staging_files(name, rev, exts=None):


Test patch: