Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(s3): Don't hardcode endpoint and region #2159

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions press/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ def backup_site(self, site, with_files=False, offsite=False):
"ACCESS_KEY": settings.offsite_backups_access_key_id,
"SECRET_KEY": settings.get_password("offsite_backups_secret_access_key"),
"REGION": backup_bucket.get("region") if isinstance(backup_bucket, dict) else "",
"ENDPOINT_URL": backup_bucket.get("endpoint_url") if isinstance(backup_bucket, dict) else "",
}
data.update({"offsite": {"bucket": bucket_name, "auth": auth, "path": backups_path}})

Expand Down
36 changes: 18 additions & 18 deletions press/api/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from frappe.core.utils import find
from frappe.desk.doctype.tag.tag import add_tag
from frappe.utils import flt, sbool, time_diff_in_hours
from frappe.utils.password import get_decrypted_password
from frappe.utils.user import is_system_user

from press.exceptions import AAAARecordExists, ConflictingCAARecord
Expand Down Expand Up @@ -47,6 +46,7 @@
from press.press.doctype.deploy_candidate_app.deploy_candidate_app import (
DeployCandidateApp,
)
from press.press.doctype.press_settings.press_settings import PressSettings


NAMESERVERS = ["1.1.1.1", "1.0.0.1", "8.8.8.8", "8.8.4.4"]
Expand Down Expand Up @@ -1779,18 +1779,18 @@

@frappe.whitelist()
def get_upload_link(file, parts=1):
bucket_name = frappe.db.get_single_value("Press Settings", "remote_uploads_bucket")
expiration = frappe.db.get_single_value("Press Settings", "remote_link_expiry") or 3600
press_settings: "PressSettings" = frappe.get_single("Press Settings") # type: ignore
bucket_name = press_settings.remote_uploads_bucket
expiration = press_settings.remote_link_expiry or 3600

Check warning on line 1784 in press/api/site.py

View check run for this annotation

Codecov / codecov/patch

press/api/site.py#L1782-L1784

Added lines #L1782 - L1784 were not covered by tests
object_name = get_remote_key(file)
parts = int(parts)

s3_client = client(
"s3",
aws_access_key_id=frappe.db.get_single_value("Press Settings", "remote_access_key_id"),
aws_secret_access_key=get_decrypted_password(
"Press Settings", "Press Settings", "remote_secret_access_key"
),
region_name="ap-south-1",
aws_access_key_id=press_settings.remote_access_key_id,
aws_secret_access_key=press_settings.get_password("remote_secret_access_key"),
region_name=press_settings.get("remote_uploads_region") or "ap-south-1",
endpoint_url=press_settings.get("remote_uploads_endpoint") or None,
)
try:
# The response contains the presigned URL and required fields
Expand Down Expand Up @@ -1822,24 +1822,24 @@

@frappe.whitelist()
def multipart_exit(file, id, action, parts=None):
press_settings: "PressSettings" = frappe.get_single("Press Settings") # type: ignore
bucket_name = "uploads.frappe.cloud" # TODO: Use press_settings.remote_uploads_bucket

Check warning on line 1826 in press/api/site.py

View check run for this annotation

Codecov / codecov/patch

press/api/site.py#L1825-L1826

Added lines #L1825 - L1826 were not covered by tests
Copy link
Contributor Author

@cogk cogk Sep 17, 2024

Choose a reason for hiding this comment

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

If the Frappe team can confirm that the current config on Frappe Cloud is indeed
remote_uploads_bucket = "uploads.frappe.cloud"

Which I believe is the case:

Suggested change
bucket_name = "uploads.frappe.cloud" # TODO: Use press_settings.remote_uploads_bucket
bucket_name = press_settings.remote_uploads_bucket


s3_client = client(
"s3",
aws_access_key_id=frappe.db.get_single_value("Press Settings", "remote_access_key_id"),
aws_secret_access_key=get_decrypted_password(
"Press Settings",
"Press Settings",
"remote_secret_access_key",
raise_exception=False,
),
region_name="ap-south-1",
aws_access_key_id=press_settings.remote_access_key_id,
aws_secret_access_key=press_settings.get_password("remote_secret_access_key", raise_exception=False),
region_name=press_settings.get("remote_uploads_region") or "ap-south-1",
endpoint_url=press_settings.get("remote_uploads_endpoint") or None,
)

if action == "abort":
response = s3_client.abort_multipart_upload(Bucket="uploads.frappe.cloud", Key=file, UploadId=id)
response = s3_client.abort_multipart_upload(Bucket=bucket_name, Key=file, UploadId=id)

Check warning on line 1837 in press/api/site.py

View check run for this annotation

Codecov / codecov/patch

press/api/site.py#L1837

Added line #L1837 was not covered by tests
elif action == "complete":
parts = json.loads(parts)
# After completing for all parts, you will use complete_multipart_upload api which requires that parts list
response = s3_client.complete_multipart_upload(
Bucket="uploads.frappe.cloud",
Bucket=bucket_name,
Key=file,
UploadId=id,
MultipartUpload={"Parts": parts},
Expand Down
35 changes: 20 additions & 15 deletions press/press/doctype/press_settings/press_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,8 @@
"backups_tab",
"offsite_backups_section",
"backup_region",
"offsite_backups_provider",
"aws_s3_bucket",
"data_40",
"offsite_backups_endpoint",
"backup_rotation_scheme",
"column_break_35",
"offsite_backups_access_key_id",
Expand Down Expand Up @@ -90,10 +89,12 @@
"auto_update_queue_size",
"remote_files_section",
"remote_uploads_bucket",
"remote_link_expiry",
"remote_uploads_region",
"remote_uploads_endpoint",
"column_break_51",
"remote_access_key_id",
"remote_secret_access_key",
"remote_link_expiry",
"product_documentation_section",
"publish_docs",
"storage_and_disk_limits_section",
Expand Down Expand Up @@ -405,22 +406,11 @@
"fieldtype": "Data",
"label": "Backup Region"
},
{
"default": "AWS S3",
"fieldname": "offsite_backups_provider",
"fieldtype": "Select",
"label": "Backup Provider",
"options": "AWS S3"
},
{
"fieldname": "aws_s3_bucket",
"fieldtype": "Data",
"label": "Bucket Name"
},
{
"fieldname": "data_40",
"fieldtype": "Data"
},
{
"fieldname": "backup_rotation_scheme",
"fieldtype": "Select",
Expand Down Expand Up @@ -1246,11 +1236,26 @@
"fieldtype": "Link",
"label": "Press Trial Plan",
"options": "Site Plan"
},
{
"fieldname": "remote_uploads_region",
"fieldtype": "Data",
"label": "Uploads Bucket Region"
},
{
"fieldname": "remote_uploads_endpoint",
"fieldtype": "Data",
"label": "Uploads Bucket Endpoint"
},
{
"fieldname": "offsite_backups_endpoint",
"fieldtype": "Data",
"label": "Bucket Endpoint"
}
],
"issingle": 1,
"links": [],
"modified": "2024-10-14 21:02:12.948747",
"modified": "2024-11-14 14:21:14.359536",
"modified_by": "Administrator",
"module": "Press",
"name": "Press Settings",
Expand Down
5 changes: 3 additions & 2 deletions press/press/doctype/press_settings/press_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ class PressSettings(Document):
code_server_password: DF.Data | None
commission: DF.Float
compress_app_cache: DF.Check
data_40: DF.Data | None
default_outgoing_id: DF.Data | None
default_outgoing_pass: DF.Data | None
disable_agent_job_deduplication: DF.Check
Expand Down Expand Up @@ -98,7 +97,7 @@ class PressSettings(Document):
ngrok_auth_token: DF.Data | None
offsite_backups_access_key_id: DF.Data | None
offsite_backups_count: DF.Int
offsite_backups_provider: DF.Literal["AWS S3"]
offsite_backups_endpoint: DF.Data | None
offsite_backups_secret_access_key: DF.Password | None
plausible_api_key: DF.Password | None
plausible_site_id: DF.Data | None
Expand All @@ -115,6 +114,8 @@ class PressSettings(Document):
remote_link_expiry: DF.Int
remote_secret_access_key: DF.Password | None
remote_uploads_bucket: DF.Data | None
remote_uploads_endpoint: DF.Data | None
remote_uploads_region: DF.Data | None
root_domain: DF.Data | None
rsa_key_size: DF.Literal["2048", "3072", "4096"]
spaces_domain: DF.Link | None
Expand Down
104 changes: 62 additions & 42 deletions press/press/doctype/remote_file/remote_file.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
# -*- coding: utf-8 -*-

Check failure on line 1 in press/press/doctype/remote_file/remote_file.py

View workflow job for this annotation

GitHub Actions / Lint and Format

Ruff (UP009)

press/press/doctype/remote_file/remote_file.py:1:1: UP009 UTF-8 encoding declaration is unnecessary
# Copyright (c) 2020, Frappe and contributors
# For license information, please see license.txt


import json
import pprint
from typing import TYPE_CHECKING

import frappe
import requests
from boto3 import client, resource
from frappe.model.document import Document
from frappe.utils.password import get_decrypted_password


if TYPE_CHECKING:

Check failure on line 16 in press/press/doctype/remote_file/remote_file.py

View workflow job for this annotation

GitHub Actions / Lint and Format

Ruff (I001)

press/press/doctype/remote_file/remote_file.py:6:1: I001 Import block is un-sorted or un-formatted
from press.press.doctype.press_settings.press_settings import PressSettings


def get_remote_key(file):
Expand All @@ -27,34 +31,30 @@


def poll_file_statuses():
aws_access_key = frappe.db.get_single_value(
"Press Settings", "offsite_backups_access_key_id"
)
aws_secret_key = get_decrypted_password(
"Press Settings", "Press Settings", "offsite_backups_secret_access_key"
)
default_region = frappe.db.get_single_value("Press Settings", "backup_region")
press_settings: "PressSettings" = frappe.get_single("Press Settings") # type: ignore

Check warning on line 34 in press/press/doctype/remote_file/remote_file.py

View check run for this annotation

Codecov / codecov/patch

press/press/doctype/remote_file/remote_file.py#L34

Added line #L34 was not covered by tests

aws_access_key = press_settings.offsite_backups_access_key_id
aws_secret_key = press_settings.offsite_backups_secret_access_key
default_region = press_settings.backup_region

Check warning on line 38 in press/press/doctype/remote_file/remote_file.py

View check run for this annotation

Codecov / codecov/patch

press/press/doctype/remote_file/remote_file.py#L36-L38

Added lines #L36 - L38 were not covered by tests

buckets = [
{
"name": frappe.db.get_single_value("Press Settings", "aws_s3_bucket"),
"name": press_settings.aws_s3_bucket,
"region": default_region,
"access_key_id": aws_access_key,
"secret_access_key": aws_secret_key,
"endpoint_url": press_settings.offsite_backups_endpoint,
},
{
"name": frappe.db.get_single_value("Press Settings", "remote_uploads_bucket"),
"region": default_region,
"access_key_id": frappe.db.get_single_value(
"Press Settings", "remote_access_key_id"
),
"secret_access_key": get_decrypted_password(
"Press Settings", "Press Settings", "remote_secret_access_key"
),
"name": press_settings.remote_uploads_bucket,
"region": press_settings.get("remote_uploads_region") or "ap-south-1",
"access_key_id": press_settings.remote_access_key_id,
"secret_access_key": press_settings.get_password("remote_secret_access_key"),
"endpoint_url": press_settings.get("remote_uploads_endpoint") or None,
},
]

for b in frappe.get_all("Backup Bucket", ["bucket_name", "cluster", "region"]):

buckets.append(
{
"name": b["bucket_name"],
Expand Down Expand Up @@ -83,6 +83,7 @@
aws_access_key_id=bucket["access_key_id"],
aws_secret_access_key=bucket["secret_access_key"],
region_name=bucket["region"],
endpoint_url=bucket.get("endpoint_url") or None,
)

available_files = set()
Expand Down Expand Up @@ -128,7 +129,7 @@
"""Delete specified objects identified by keys in the backups bucket."""
remote_files = list(set([x for x in remote_files if x]))
if not remote_files:
return

Check failure on line 132 in press/press/doctype/remote_file/remote_file.py

View workflow job for this annotation

GitHub Actions / Lint and Format

Ruff (RET502)

press/press/doctype/remote_file/remote_file.py:132:3: RET502 Do not implicitly `return None` in function able to return non-`None` value

buckets = {bucket: [] for bucket in frappe.get_all("Backup Bucket", pluck="name")}
buckets.update({frappe.db.get_single_value("Press Settings", "aws_s3_bucket"): []})
Expand Down Expand Up @@ -159,14 +160,14 @@
if TYPE_CHECKING:
from frappe.types import DF

bucket: DF.Data | None

Check failure on line 163 in press/press/doctype/remote_file/remote_file.py

View workflow job for this annotation

GitHub Actions / Lint and Format

Ruff (FA102)

press/press/doctype/remote_file/remote_file.py:163:11: FA102 Missing `from __future__ import annotations`, but uses PEP 604 union
file_name: DF.Data | None

Check failure on line 164 in press/press/doctype/remote_file/remote_file.py

View workflow job for this annotation

GitHub Actions / Lint and Format

Ruff (FA102)

press/press/doctype/remote_file/remote_file.py:164:14: FA102 Missing `from __future__ import annotations`, but uses PEP 604 union
file_path: DF.Text | None

Check failure on line 165 in press/press/doctype/remote_file/remote_file.py

View workflow job for this annotation

GitHub Actions / Lint and Format

Ruff (FA102)

press/press/doctype/remote_file/remote_file.py:165:14: FA102 Missing `from __future__ import annotations`, but uses PEP 604 union
file_size: DF.Data | None

Check failure on line 166 in press/press/doctype/remote_file/remote_file.py

View workflow job for this annotation

GitHub Actions / Lint and Format

Ruff (FA102)

press/press/doctype/remote_file/remote_file.py:166:14: FA102 Missing `from __future__ import annotations`, but uses PEP 604 union
file_type: DF.Data | None

Check failure on line 167 in press/press/doctype/remote_file/remote_file.py

View workflow job for this annotation

GitHub Actions / Lint and Format

Ruff (FA102)

press/press/doctype/remote_file/remote_file.py:167:14: FA102 Missing `from __future__ import annotations`, but uses PEP 604 union
site: DF.Link | None

Check failure on line 168 in press/press/doctype/remote_file/remote_file.py

View workflow job for this annotation

GitHub Actions / Lint and Format

Ruff (FA102)

press/press/doctype/remote_file/remote_file.py:168:9: FA102 Missing `from __future__ import annotations`, but uses PEP 604 union
status: DF.Literal["Available", "Unavailable"]
url: DF.Code | None

Check failure on line 170 in press/press/doctype/remote_file/remote_file.py

View workflow job for this annotation

GitHub Actions / Lint and Format

Ruff (FA102)

press/press/doctype/remote_file/remote_file.py:170:8: FA102 Missing `from __future__ import annotations`, but uses PEP 604 union
# end: auto-generated types

@property
Expand All @@ -174,31 +175,32 @@
if not self.bucket:
return None

elif self.bucket == frappe.db.get_single_value(
"Press Settings", "remote_uploads_bucket"
):
access_key_id = frappe.db.get_single_value("Press Settings", "remote_access_key_id")
secret_access_key = get_decrypted_password(
"Press Settings", "Press Settings", "remote_secret_access_key"
)
press_settings: "PressSettings" = frappe.get_single("Press Settings") # type: ignore

Check warning on line 178 in press/press/doctype/remote_file/remote_file.py

View check run for this annotation

Codecov / codecov/patch

press/press/doctype/remote_file/remote_file.py#L178

Added line #L178 was not covered by tests

elif self.bucket:
access_key_id = frappe.db.get_single_value(
"Press Settings", "offsite_backups_access_key_id"
if self.bucket == press_settings.remote_uploads_bucket:
access_key_id = press_settings.remote_access_key_id
secret_access_key = press_settings.get_password("remote_secret_access_key")
region_name = press_settings.get("remote_uploads_region") or "ap-south-1"
endpoint_url = press_settings.get("remote_uploads_endpoint") or None

Check warning on line 184 in press/press/doctype/remote_file/remote_file.py

View check run for this annotation

Codecov / codecov/patch

press/press/doctype/remote_file/remote_file.py#L180-L184

Added lines #L180 - L184 were not covered by tests
else:
access_key_id = press_settings.offsite_backups_access_key_id
secret_access_key = press_settings.get_password("offsite_backups_secret_access_key")
region_name = (

Check warning on line 188 in press/press/doctype/remote_file/remote_file.py

View check run for this annotation

Codecov / codecov/patch

press/press/doctype/remote_file/remote_file.py#L186-L188

Added lines #L186 - L188 were not covered by tests
frappe.db.get_value("Backup Bucket", self.bucket, "region")
or press_settings.backup_region
)
secret_access_key = get_decrypted_password(
"Press Settings", "Press Settings", "offsite_backups_secret_access_key"
endpoint_url = (

Check warning on line 192 in press/press/doctype/remote_file/remote_file.py

View check run for this annotation

Codecov / codecov/patch

press/press/doctype/remote_file/remote_file.py#L192

Added line #L192 was not covered by tests
frappe.db.get_value("Backup Bucket", self.bucket, "endpoint_url")
or press_settings.get("offsite_backups_endpoint")
or None
)

else:
return None

return client(
"s3",
aws_access_key_id=access_key_id,
aws_secret_access_key=secret_access_key,
region_name=frappe.db.get_value("Backup Bucket", self.bucket, "region")
or frappe.db.get_single_value("Press Settings", "backup_region"),
region_name=region_name,
endpoint_url=endpoint_url or None,
)

@property
Expand Down Expand Up @@ -270,16 +272,34 @@

from press.utils import chunk

press_settings = frappe.get_single("Press Settings")
press_settings: "PressSettings" = frappe.get_single("Press Settings") # type: ignore

Check warning on line 275 in press/press/doctype/remote_file/remote_file.py

View check run for this annotation

Codecov / codecov/patch

press/press/doctype/remote_file/remote_file.py#L275

Added line #L275 was not covered by tests
for bucket_name in buckets.keys():
if bucket_name == press_settings.aws_s3_bucket:
endpoint_url = press_settings.offsite_backups_endpoint
region_name = press_settings.backup_region
aws_access_key_id = press_settings.aws_access_key_id
aws_secret_access_key = press_settings.aws_secret_access_key
elif bucket_name == press_settings.remote_uploads_bucket:
endpoint_url = press_settings.remote_uploads_endpoint
region_name = press_settings.remote_uploads_region
aws_access_key_id = press_settings.offsite_backups_access_key_id
aws_secret_access_key = press_settings.get_password(

Check warning on line 286 in press/press/doctype/remote_file/remote_file.py

View check run for this annotation

Codecov / codecov/patch

press/press/doctype/remote_file/remote_file.py#L277-L286

Added lines #L277 - L286 were not covered by tests
"offsite_backups_secret_access_key", raise_exception=False
)
else:
endpoint_url = frappe.db.get_value("Backup Bucket", bucket_name, "endpoint_url")
region_name = frappe.db.get_value("Backup Bucket", bucket_name, "region")
aws_access_key_id = press_settings.offsite_backups_access_key_id
aws_secret_access_key = press_settings.get_password(

Check warning on line 293 in press/press/doctype/remote_file/remote_file.py

View check run for this annotation

Codecov / codecov/patch

press/press/doctype/remote_file/remote_file.py#L290-L293

Added lines #L290 - L293 were not covered by tests
"offsite_backups_secret_access_key", raise_exception=False
)

s3 = resource(
"s3",
aws_access_key_id=press_settings.offsite_backups_access_key_id,
aws_secret_access_key=press_settings.get_password(
"offsite_backups_secret_access_key", raise_exception=False
),
endpoint_url=frappe.db.get_value("Backup Bucket", bucket_name, "endpoint_url")
or "https://s3.amazonaws.com",
aws_access_key_id=aws_access_key_id,
aws_secret_access_key=aws_secret_access_key,
region_name=region_name or "ap-south-1",
endpoint_url=endpoint_url or "https://s3.amazonaws.com",
)
bucket = s3.Bucket(bucket_name)
for objects in chunk([{"Key": x} for x in buckets[bucket_name]], 1000):
Expand Down
20 changes: 16 additions & 4 deletions press/press/doctype/site_backup/site_backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

if TYPE_CHECKING:
from datetime import datetime
from press.press.doctype.press_settings.press_settings import PressSettings


class SiteBackup(Document):
Expand Down Expand Up @@ -208,12 +209,23 @@ def process_backup_site_job_update(job):


def get_backup_bucket(cluster, region=False):
bucket_for_cluster = frappe.get_all("Backup Bucket", {"cluster": cluster}, ["name", "region"], limit=1)
default_bucket = frappe.db.get_single_value("Press Settings", "aws_s3_bucket")
bucket_for_cluster = frappe.get_all(
"Backup Bucket", {"cluster": cluster}, ["name", "region", "endpoint_url"], limit=1
)

if not bucket_for_cluster:
press_settings: "PressSettings" = frappe.get_single("Press Settings") # type: ignore
bucket_for_cluster = [
{
"name": press_settings.aws_s3_bucket,
"region": press_settings.backup_region,
"endpoint_url": press_settings.offsite_backups_endpoint,
}
]

if region:
return bucket_for_cluster[0] if bucket_for_cluster else default_bucket
return bucket_for_cluster[0]["name"] if bucket_for_cluster else default_bucket
return bucket_for_cluster[0]
return bucket_for_cluster[0]["name"]


def on_doctype_update():
Expand Down
Loading