Skip to content

Delete archived letter attachments from s3#4894

Open
CrystalPea wants to merge 10 commits into
mainfrom
delete-archived-letter-attachments-from-s3
Open

Delete archived letter attachments from s3#4894
CrystalPea wants to merge 10 commits into
mainfrom
delete-archived-letter-attachments-from-s3

Conversation

@CrystalPea

@CrystalPea CrystalPea commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

I used remove_archived_template_email_files_from_s3() task as a base for this new task.

I had to adjust it, as letter attachments are a bit different. I did some refactoring on the way, and de-duplicated the code shared between the tasks.

I have pushed these changes to dev-b, and functional tests pass: https://concourse.notify.tools/teams/dev-b/pipelines/deploy-notify/jobs/functional-tests/builds/520

I also did manual testing.

I needed to make letter attachments S3 bucket available to api apps: https://github.com/alphagov/notifications-aws/pull/3045 (this needs to be merged / possibly terraform to be run first)

After that, the task worked as expected:
Screenshot 2026-07-09 at 16 08 40

Function based on a similar task for template email files
@CrystalPea
CrystalPea force-pushed the delete-archived-letter-attachments-from-s3 branch 2 times, most recently from 1e2c76e to 5599157 Compare June 25, 2026 10:57
for scoped files.

Also move archiving a letter attachment into a dao method
- that's a more appropriate place for it.
The scoping datetimes archived_before and archived_after were
defined as timezone-aware datetimes, which didn't play well
with the naive datetimes from the db (for archived_at) field.

After a Slack discussion, I decided to try the approach from
sqlalchemy docs to try and fix it:
https://docs.sqlalchemy.org/en/20/core/custom_types.html#store-timezone-aware-timestamps-as-timezone-naive-utc

The scoping now works as expected.
@CrystalPea
CrystalPea force-pushed the delete-archived-letter-attachments-from-s3 branch from 5599157 to d809694 Compare June 25, 2026 14:42
@CrystalPea
CrystalPea force-pushed the delete-archived-letter-attachments-from-s3 branch 2 times, most recently from d7abe81 to 5f4d19a Compare June 25, 2026 17:12
between two tasks instead of duplicating it.
@CrystalPea
CrystalPea force-pushed the delete-archived-letter-attachments-from-s3 branch from 5f4d19a to f13be34 Compare June 26, 2026 14:57
@CrystalPea
CrystalPea marked this pull request as ready for review June 26, 2026 15:25
Comment thread app/config.py
S3_BUCKET_INVALID_PDF = os.environ.get("S3_BUCKET_INVALID_PDF")
S3_BUCKET_TRANSIENT_UPLOADED_LETTERS = os.environ.get("S3_BUCKET_TRANSIENT_UPLOADED_LETTERS")
S3_BUCKET_LETTER_SANITISE = os.environ.get("S3_BUCKET_LETTER_SANITISE")
S3_BUCKET_LETTER_ATTACHMENTS = os.environ.get("S3_BUCKET_LETTER_ATTACHMENTS", "local-letter-attachments")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we follow the pattern of other buckets and have os.environ.get("S3_BUCKET_LETTER_ATTACHMENTS") here, and then hardcode development-letter-attachments in the Development config and test-letter-attachments in the Test config?

It keeps it consistent, plus we don't have buckets with the prefix of local-, so there isn't a bucket called local-letter-attachments

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The pattern for S3_BUCKET_TEMPLATE_EMAIL_FILES

S3_BUCKET_TEMPLATE_EMAIL_FILES = os.environ.get("S3_BUCKET_TEMPLATE_EMAIL_FILES")
is a good example

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

sure thing, I will change it 🙌🏼

bucket_name = current_app.config["S3_BUCKET_TEMPLATE_EMAIL_FILES"]

@notify_celery.task(name="remove-archived-letter-attachments-from-s3")
@cronitor("remove-archived-letter-attachments-from-s3")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I couldn't see a corresponding credentials PR, but we will need one when this is ready to be merged (https://github.com/alphagov/notifications-manuals/wiki/Monitoring-and-alerting#cronitor)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ahh, good spot, I will follow the process in the manual 🙌🏼

failed += 1
def _get_archived_before_and_after(archived_after):
archived_before = (
(datetime.now(UTC) - timedelta(days=ARCHIVED_FILE_RETENTION_DAYS)).astimezone(UTC).replace(tzinfo=None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since timedelta is acting on datetime.now(UTC) which is already UTC aware, isn't astimezone(UTC) redundant here?

@BlessedDev BlessedDev Jul 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

.ie would timedelta(days=ARCHIVED_FILE_RETENTION_DAYS)).replace(tzinfo=None) be sufficient


if archived_after > archived_before:
raise ValueError(
f"Invalid archived_after for s3 cleanup of archived files: "

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This aspect of the refactoring makes the error message less clear, it replaces "Invalid archived_after for archived template email file s3 cleanup: " and "Invalid archived_after for archived letter attachment s3 cleanup: " with less specificity.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I thought it wouldn't matter since this error message can only be seen when running the task manually - and then that person knows which task they are running.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ok. I see your point.

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.

3 participants