diff --git a/app/replication.py b/app/replication.py index b9d86ee..994bd7b 100644 --- a/app/replication.py +++ b/app/replication.py @@ -66,6 +66,25 @@ class ReplicationManager: del self._rules[bucket_name] self.save_rules() + def create_remote_bucket(self, connection_id: str, bucket_name: str) -> None: + """Create a bucket on the remote connection.""" + connection = self.connections.get(connection_id) + if not connection: + raise ValueError(f"Connection {connection_id} not found") + + try: + s3 = boto3.client( + "s3", + endpoint_url=connection.endpoint_url, + aws_access_key_id=connection.access_key, + aws_secret_access_key=connection.secret_key, + region_name=connection.region, + ) + s3.create_bucket(Bucket=bucket_name) + except ClientError as e: + logger.error(f"Failed to create remote bucket {bucket_name}: {e}") + raise + def trigger_replication(self, bucket_name: str, object_key: str) -> None: rule = self.get_rule(bucket_name) if not rule or not rule.enabled: diff --git a/app/s3_api.py b/app/s3_api.py index 584074b..f9d496f 100644 --- a/app/s3_api.py +++ b/app/s3_api.py @@ -8,7 +8,7 @@ import re import uuid from datetime import datetime, timedelta, timezone from typing import Any, Dict -from urllib.parse import quote, urlencode +from urllib.parse import quote, urlencode, urlparse from xml.etree.ElementTree import Element, SubElement, tostring, fromstring, ParseError from flask import Blueprint, Response, current_app, jsonify, request @@ -468,7 +468,17 @@ def _generate_presigned_url( "X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD", } canonical_query = _encode_query_params(query_params) - host = request.host + + # Determine host and scheme from config or request + api_base = current_app.config.get("API_BASE_URL") + if api_base: + parsed = urlparse(api_base) + host = parsed.netloc + scheme = parsed.scheme + else: + host = request.host + scheme = request.scheme or "http" + canonical_headers = f"host:{host}\n" canonical_request = "\n".join( [ @@ -492,7 +502,6 @@ def _generate_presigned_url( signing_key = _derive_signing_key(secret_key, date_stamp, region, service) signature = hmac.new(signing_key, string_to_sign.encode(), hashlib.sha256).hexdigest() query_with_sig = canonical_query + f"&X-Amz-Signature={signature}" - scheme = request.scheme or "http" return f"{scheme}://{host}{_canonical_uri(bucket_name, object_key)}?{query_with_sig}" diff --git a/app/ui.py b/app/ui.py index ebc04ca..fbf48a3 100644 --- a/app/ui.py +++ b/app/ui.py @@ -1105,6 +1105,16 @@ def update_bucket_replication(bucket_name: str): if not target_conn_id or not target_bucket: flash("Target connection and bucket are required", "danger") else: + # Check if user wants to create the remote bucket + create_remote = request.form.get("create_remote_bucket") == "on" + if create_remote: + try: + _replication().create_remote_bucket(target_conn_id, target_bucket) + flash(f"Created remote bucket '{target_bucket}'", "success") + except Exception as e: + flash(f"Failed to create remote bucket: {e}", "warning") + # We continue to set the rule even if creation fails (maybe it exists?) + rule = ReplicationRule( bucket_name=bucket_name, target_connection_id=target_conn_id, diff --git a/templates/bucket_detail.html b/templates/bucket_detail.html index af93fe8..df6604b 100644 --- a/templates/bucket_detail.html +++ b/templates/bucket_detail.html @@ -409,6 +409,7 @@
+
@@ -418,6 +419,7 @@ {% if connections %}
+
@@ -434,7 +436,12 @@
-
The bucket on the remote service must already exist.
+
+ + +