Release v0.1.1 #1
@@ -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:
|
||||
|
||||
@@ -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}"
|
||||
|
||||
|
||||
|
||||
10
app/ui.py
10
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,
|
||||
|
||||
@@ -409,6 +409,7 @@
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ url_for('ui.update_bucket_replication', bucket_name=bucket_name) }}" onsubmit="return confirm('Are you sure you want to disable replication?');">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<input type="hidden" name="action" value="delete">
|
||||
<button type="submit" class="btn btn-danger">Disable Replication</button>
|
||||
</form>
|
||||
@@ -418,6 +419,7 @@
|
||||
|
||||
{% if connections %}
|
||||
<form method="POST" action="{{ url_for('ui.update_bucket_replication', bucket_name=bucket_name) }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<input type="hidden" name="action" value="create">
|
||||
|
||||
<div class="mb-3">
|
||||
@@ -434,7 +436,12 @@
|
||||
<div class="mb-3">
|
||||
<label for="target_bucket" class="form-label">Target Bucket Name</label>
|
||||
<input type="text" class="form-control" id="target_bucket" name="target_bucket" required placeholder="e.g. my-backup-bucket">
|
||||
<div class="form-text">The bucket on the remote service must already exist.</div>
|
||||
<div class="form-check mt-2">
|
||||
<input class="form-check-input" type="checkbox" id="create_remote_bucket" name="create_remote_bucket">
|
||||
<label class="form-check-label" for="create_remote_bucket">
|
||||
Create this bucket on the remote server if it doesn't exist
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Enable Replication</button>
|
||||
|
||||
Reference in New Issue
Block a user