Skip to content

Add backup policy migration support#145

Open
premtsd-code wants to merge 9 commits intomainfrom
feat-backup-policy-migration
Open

Add backup policy migration support#145
premtsd-code wants to merge 9 commits intomainfrom
feat-backup-policy-migration

Conversation

@premtsd-code
Copy link
Contributor

@premtsd-code premtsd-code commented Feb 5, 2026

Summary

  • Add backup policy migration support for Appwrite-to-Appwrite migrations
  • Export policies via API from source project
  • Import policies via API in destination project
  • Supported service policy migration - Database, Bucket, Function
  • Supported resource policy migration - Database, Bucket

Summary by CodeRabbit

  • New Features

    • Backup policy migration: export, import and reporting for backup policies; backups added as a new resource group with batching support.
  • Behavior

    • Reporting clarifications for missing permissions or plan limitations; destinations handle backups gracefully and per-resource validation is applied.
    • Source adapters expose backup export hooks; some adapters provide not-yet-implemented stubs.
  • Tests

    • Unit mocks updated to include the backup policy resource.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds backup policy support across the migration system: new Resource::TYPE_BACKUP_POLICY and Transfer::GROUP_BACKUPS; a Policy resource class; Source gains batching and an abstract exportGroupBackups hook; Appwrite source implements export/report/exportGroupBackups/reportBackups/exportBackupPolicies with API calls to /backups/policies and permission/plan-specific error handling; Appwrite destination adds importBackupResource, routing, validation, and create/update logic; CSV, Firebase, JSON, NHost, MockSource, and test adapters receive exportGroupBackups stubs or implementations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding backup policy migration support. It is concise, clear, and reflects the primary feature introduced across all modified files.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-backup-policy-migration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/Migration/Destinations/Appwrite.php`:
- Around line 213-240: The POST check for backup policy scope (in the block
guarded by Resource::TYPE_BACKUP_POLICY where call() is used against
'/backups/policies' and the $scope variable is set) currently only treats 401s
specially; update the catch in that try/catch to also handle 400 and 404 status
codes: decode the error body from $e->getMessage(), and if the code is 400 or
404, swallow the error (treat as non-fatal so migrations continue) while
preserving other behavior for 401 (keep the existing
additional_resource_not_allowed handling and Missing scope exception); leave
other throw behavior unchanged. Ensure the logic references the same $scope
variable and call() invocations so it applies to the POST validation.

In `@src/Migration/Sources/Appwrite.php`:
- Around line 184-185: The current generic exception handler around backup
policy reporting masks permission errors; update the catch block inside the
Appwrite migration code that calls reportBackups (the code that uses
filterByIdArray() with $resourceIds) to inspect the exception type/code: if the
exception indicates an authorization/permission failure (HTTP 401 or 403 from
the Appwrite client exception), surface or rethrow it (or log it as a permission
error) so missing policies.read scope is visible; for other errors (404/501 or
other API-unavailable cases) continue to treat it as "0 policies" as before. Use
the exception's getCode()/getStatusCode() and the reportBackups/filterByIdArray
symbols to locate the handler and implement the conditional handling
accordingly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/Migration/Destinations/Appwrite.php`:
- Around line 1488-1524: The POST payload omits the resourceType even though the
import code reads and validates it; update the logic in the Appwrite destination
(the block that builds $params and the conditional that checks
$resource->getResourceId() and $collection) to add a 'resourceType' key to
$params whenever you set 'resourceId' (i.e., for supported collections
determined by match on $resource->getResourceType()), so the $this->call('POST',
'/backups/policies', ...) sends both resourceId and resourceType alongside
policyId, name, services, enabled, retention and schedule.
🧹 Nitpick comments (1)
src/Migration/Destinations/Appwrite.php (1)

1497-1517: Silently dropping resourceId for unsupported resource types may mask bugs.

When $resource->getResourceType() returns something other than TYPE_DATABASE or TYPE_BUCKET, the match falls through to null and the resourceId is silently omitted from the API call. Consider logging a warning or throwing when an unexpected resource type has a non-null resourceId, so future additions don't silently break.

♻️ Suggested approach
                     $collection = match ($resource->getResourceType()) {
                         Resource::TYPE_DATABASE => 'databases',
                         Resource::TYPE_BUCKET => 'buckets',
-                        default => null, // Only databases and buckets support per-resource backup policies
+                        default => throw new Exception(
+                            resourceName: $resource->getName(),
+                            resourceGroup: $resource->getGroup(),
+                            resourceId: $resource->getId(),
+                            message: 'Unsupported resource type for backup policy: ' . $resource->getResourceType(),
+                        ),
                     };
-
-                    // Only pass resourceId for supported resource types
-                    if ($collection !== null) {
-                        $doc = $this->database->getDocument($collection, $resource->getResourceId());

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/Migration/Destinations/Appwrite.php`:
- Around line 1516-1533: The code unconditionally calls
$resource->setStatus(Resource::STATUS_SUCCESS) after the API call which
overwrites any prior warning set during the per-resource type check; change the
logic around $resource->setStatus so you only set STATUS_SUCCESS when the
current status is not Resource::STATUS_WARNING (or otherwise merge/append
messages), e.g. check $resource->getStatus() (or a status flag on $resource)
after the API call and avoid calling setStatus(Resource::STATUS_SUCCESS) if it
already equals Resource::STATUS_WARNING, preserving the original warning about
unsupported resource types.
🧹 Nitpick comments (2)
src/Migration/Sources/Appwrite.php (2)

1642-1666: $resourceIds parameter is accepted but unused.

Unlike other report* methods (e.g., reportAuth, reportStorage) that pass $resourceIds into buildQueries() for filtering, reportBackups ignores it entirely. If the /backups/policies endpoint supports query filtering, consider applying $resourceIds to the request; otherwise, document why filtering isn't applicable here. Static analysis also flags this (PHPMD UnusedFormalParameter).


1672-1698: $batchSize is unused — no pagination for backup policies.

Every other export* method uses $batchSize for cursor-based pagination. Here, all policies are fetched in a single call. If the endpoint doesn't support pagination this is fine, but the unused parameter is flagged by static analysis (PHPMD UnusedFormalParameter). Consider either adding pagination or documenting why it's unnecessary (e.g., policy count is inherently bounded).

Comment on lines 1649 to 1651
$response = $this->call('GET', '/backups/policies', [
'Content-Type' => 'application/json',
]);
Copy link
Member

Choose a reason for hiding this comment

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

Let's add a TODO to use the SDK once these methods are there

Resolve conflicts keeping both backups and sites support.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/Migration/Sources/Appwrite.php`:
- Around line 1764-1776: In the catch block that handles exceptions from
$this->call(), avoid treating non-API errors as "0 policies": after decoding
with json_decode($e->getMessage(), true) check if $body === null (or
!is_array($body)); if it's not a parsed API response then re-throw the original
exception (or throw a new exception preserving $e as previous) so
network/timeout/DNS failures are not masked, otherwise continue the existing
code-path that reads $body['code'] and only maps 401/403 to a permission
exception and fallback to setting $report[Resource::TYPE_BACKUP_POLICY] = 0 for
Cloud-feature-not-available cases.
🧹 Nitpick comments (1)
src/Migration/Sources/Appwrite.php (1)

1783-1809: $batchSize unused; single-call fetch has no pagination.

$batchSize is required by the abstract method signature, so it must be accepted — but this method fetches all policies in a single API call without pagination. This is fine given that backup policies are typically few, but worth noting for future scalability.

Also, the default $policy['enabled'] ?? true on line 1802 means a missing enabled field results in the policy being created as enabled on the destination. If the field is ever absent, this could accidentally activate policies. Consider defaulting to false for safer behavior.

Comment on lines 1764 to 1776
} catch (\Throwable $e) {
$body = \json_decode($e->getMessage(), true);
$code = $body['code'] ?? 0;

// Re-throw permission errors (401/403) as they indicate configuration issues
if ($code === 401 || $code === 403) {
throw new \Exception('Missing permission to access backup policies: ' . $e->getMessage(), previous: $e);
}

// For other errors (404/501), treat as feature not available - skip gracefully
// Backup policies are Cloud-only, may not be available on self-hosted
$report[Resource::TYPE_BACKUP_POLICY] = 0;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Network/non-API errors silently treated as "0 policies".

If $this->call() throws a non-API exception (e.g., network timeout, DNS failure), json_decode($e->getMessage(), true) returns null, $code becomes 0, and the catch falls through to setting report[...] = 0 — silently masking a connectivity problem as "no backup policies."

Consider checking $body === null (indicating a non-JSON message) before the code-based branching, and either re-throwing or logging a warning for unparseable exceptions.

Suggested fix
         } catch (\Throwable $e) {
             $body = \json_decode($e->getMessage(), true);
+
+            // If exception message isn't JSON, it's likely a network/system error — don't silently swallow
+            if ($body === null) {
+                throw $e;
+            }
+
             $code = $body['code'] ?? 0;
🤖 Prompt for AI Agents
In `@src/Migration/Sources/Appwrite.php` around lines 1764 - 1776, In the catch
block that handles exceptions from $this->call(), avoid treating non-API errors
as "0 policies": after decoding with json_decode($e->getMessage(), true) check
if $body === null (or !is_array($body)); if it's not a parsed API response then
re-throw the original exception (or throw a new exception preserving $e as
previous) so network/timeout/DNS failures are not masked, otherwise continue the
existing code-path that reads $body['code'] and only maps 401/403 to a
permission exception and fallback to setting
$report[Resource::TYPE_BACKUP_POLICY] = 0 for Cloud-feature-not-available cases.

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.

2 participants