Skip to content

feat(platform): add foundationdb backups - #2606

Merged
Andrey Kolkov (androndo) merged 1 commit into
mainfrom
feat/foundationdb-backup
May 13, 2026
Merged

Andrey Kolkov (androndo) merged 1 commit into
mainfrom
feat/foundationdb-backup

Conversation

@androndo

@androndo Andrey Kolkov (androndo) commented May 11, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

Screenshots

Release note

implement backup controller for foundationdb backups 

Summary by CodeRabbit

  • New Features

    • Added FoundationDB backup & restore support via a new FoundationDB backup strategy and controller; controller now handles FoundationDB backup/restore jobs.
  • Examples

    • Added a complete FoundationDB backup/restore demo with helper scripts, step-by-step step scripts, run-all, and cleanup tooling.
  • Documentation

    • New admin and tenant guides plus a README explaining the example workflow and verification steps.
  • Deprecations

    • In-chart FoundationDB backup configuration marked DEPRECATED—migrate to BackupClass + FoundationDB strategy.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 11, 2026 •

Copy link
Copy Markdown
Contributor

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

Walkthrough

Adds a FoundationDB backup/restore strategy: public strategy CRDs, internal app/operator type mirrors, controller scheme/dispatch wiring, strategy reconciler with helpers, comprehensive tests, example scripts and docs, RBAC, and deprecation notices for legacy in-chart backups.

Changes

FoundationDB Backup Strategy Implementation

Layer / File(s) Summary
Backup strategy CRD types and deepcopy
api/backups/strategy/v1alpha1/foundationdb_types.go, api/backups/strategy/v1alpha1/zz_generated.deepcopy.go
Public CRD defines FoundationDB strategy root/list/spec/status with templated blob-store configuration and optional tuning fields; autogenerated deepcopy methods added.
Internal FoundationDB app type mirror
internal/backupcontroller/foundationdbapp/types.go, internal/backupcontroller/foundationdbapp/zz_generated.deepcopy.go
Minimal typed mirror for apps.cozystack.io/v1alpha1 FoundationDB used by the reconciler; group/version/kind constants, scheme registration, and deepcopy helpers.
Operator FoundationDB type mirrors
internal/backupcontroller/foundationdbtypes/types.go, internal/backupcontroller/foundationdbtypes/zz_generated.deepcopy.go
Mirrors of operator CRDs (Cluster, Backup, Restore) and nested structs (blob-store config, backup details, restore key ranges) plus deep-copy helpers for typed controller interactions.
Controller scheme registration & dispatch wiring
cmd/backupstrategy-controller/main.go, internal/backupcontroller/backupjob_controller.go, internal/backupcontroller/restorejob_controller.go, *_test.go
Registers FoundationDB types in controller scheme; adds dispatch cases to route BackupJob/RestoreJob to FoundationDB handlers; adds/updates tests validating dispatch and cleanup behavior.
FoundationDB backup/restore strategy reconciler
internal/backupcontroller/foundationdbstrategy_controller.go
Core reconciliation for BackupJob and RestoreJob: validates app refs, renders strategy templates, ensures operator CRs, enforces single-running-backup per cluster, waits for operator snapshot readiness with deadlines, persists Cozystack Backup artifacts (with encoded snapshot payload), resolves restore targets, parses restore options, and manages restore lifecycle.
Reconciler helpers and payloads
internal/backupcontroller/foundationdbstrategy_controller.go (helpers)
Template rendering, snapshot payload marshal/decode, restore options parsing, effective-deadline computation, operator readiness diagnostics, URI synthesis, and small utilities.
Strategy controller comprehensive tests
internal/backupcontroller/foundationdbstrategy_controller_test.go
20+ unit tests: ApplicationRef validation, cluster naming, template rendering, backup/restore idempotency, prior-backup enforcement, snapshot encode/decode, restore blob selection rules, readiness gating, restore options parsing, deadline logic, URI synthesis, and regression/gate tests.
Example helpers and setup scripts
examples/backups/foundationdb/00-helpers.sh, 01-create-strategy.sh, 02-create-backupclass.sh, 03-create-bucket.sh
Bash helpers for logging/polling/fdbcli and setup scripts to create strategy, BackupClass, provision Bucket and materialize credentials, and prepare source FoundationDB cluster.
Backup and restore example workflows
examples/backups/foundationdb/04-create-foundationdb-src.sh, 05-create-backupjob.sh, 06-restore-in-place.sh, 07-restore-to-copy.sh, cleanup.sh, run-all.sh
Demo workflows: submit BackupJob and poll snapshot readiness; two restore variants (in-place and to-copy) with sentinel verification; cleanup and orchestration scripts.
Example documentation and scenario guides
examples/backups/foundationdb/90-92-*.md, examples/backups/foundationdb/README.md
Admin setup guide and tenant-focused backup/restore scenario docs describing the flow, verification commands, and prerequisites.
RBAC updates and in-chart backup deprecation
packages/system/backupstrategy-controller/templates/rbac.yaml, api/apps/v1alpha1/foundationdb/types.go, packages/apps/foundationdb/*.{md,yaml,json}, packages/system/foundationdb-rd/cozyrds/foundationdb.yaml
ClusterRole additions to permit reading operator clusters and creating/patching per-job backups/restores. Deprecation notices added to in-chart backup API comments, Helm values JSDoc, and values.schema.json/docs directing users to BackupClass + FoundationDB strategy.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

"A rabbit hops through backup snapshots, singing with glee,
FoundationDB's data now safe as can be!
Restore in-place or copy to new ground,
With strategies and scripts, safe backups are found." 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.90% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding FoundationDB backup support to the platform.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/foundationdb-backup

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.

@github-actions github-actions Bot added area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) kind/feature Categorizes issue or PR as related to a new feature labels May 11, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new backup framework for FoundationDB and MariaDB applications by delegating backup and restore operations to their respective Kubernetes operators. By moving away from in-chart backup mechanisms, this change provides a more robust, standardized, and extensible approach to data protection within the Cozystack platform. It includes comprehensive API definitions, controller logic for managing backup and restore jobs, and detailed documentation and examples for users to migrate to the new strategy-based workflow.

Highlights

  • FoundationDB Backup Strategy: Implemented a new backup strategy for FoundationDB that leverages the FoundationDB Kubernetes operator, enabling continuous backup agent deployment to blob storage.
  • MariaDB Backup Strategy: Added a new backup strategy for MariaDB that integrates with the mariadb-operator to manage logical backups.
  • Deprecation of In-Chart Backups: Marked existing in-chart backup configurations for FoundationDB and MariaDB as deprecated, encouraging the use of the new Cozystack BackupClass and strategy-based framework.
  • Controller Integration: Updated the backupstrategy-controller to include the new MariaDB and FoundationDB controllers, ensuring proper registration and reconciliation.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Ignored Files
  • Ignored by pattern: **/zz_generated.*.go (5)
    • api/backups/strategy/v1alpha1/zz_generated.deepcopy.go
    • internal/backupcontroller/foundationdbapp/zz_generated.deepcopy.go
    • internal/backupcontroller/foundationdbtypes/zz_generated.deepcopy.go
    • internal/backupcontroller/mariadbapp/zz_generated.deepcopy.go
    • internal/backupcontroller/mariadbtypes/zz_generated.deepcopy.go
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment Gemini (@gemini-code-assist) Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request introduces a new backup and restore strategy for FoundationDB and MariaDB applications by integrating with the operator-native APIs (k8s.mariadb.com and apps.foundationdb.org). It adds the necessary controllers, types, and RBAC permissions to support these strategies, along with comprehensive documentation and example scripts for end-to-end backup/restore workflows. The changes also include deprecation notices for the legacy in-chart backup mechanisms in both the MariaDB and FoundationDB charts. My review identified a few minor issues regarding error handling and robustness in the FoundationDB strategy controller, which are addressed in the feedback.

Comment on lines +117 to +122
- apiGroups: ["k8s.mariadb.com"]
resources: ["mariadbs"]
verbs: ["get", "list", "watch"]
- apiGroups: ["k8s.mariadb.com"]
resources: ["backups", "restores"]
verbs: ["get", "list", "watch", "create"]

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.

security-medium medium

The RBAC rules for k8s.mariadb.com include create permissions for backups and restores. Ensure that the controller-runtime manager is configured with a restricted namespace if Cozystack's security model requires tenant isolation at the API level, as this allows the controller to create these resources in any namespace it can watch.

References
  1. Flag overly broad RBAC permissions. (link)

if err := json.Unmarshal(raw.Raw, snap); err != nil {
return nil, err
}
if snap.Kind != foundationdbBackupSnapshotKind {

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.

low

The check if snap.Kind != foundationdbBackupSnapshotKind returns nil, nil. This makes it indistinguishable from a missing snapshot. It would be safer to return an error if the Kind is present but incorrect, as it indicates the UnderlyingResources field contains an unexpected type.

Suggested change
if snap.Kind != foundationdbBackupSnapshotKind {
if snap.Kind != foundationdbBackupSnapshotKind {
return nil, fmt.Errorf("unexpected snapshot kind: %s", snap.Kind)
}

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 5

🧹 Nitpick comments (3)
api/backups/strategy/v1alpha1/mariadb_types.go (1)

101-119: ⚡ Quick win

Consider adding CRD validation for the storage type constraint.

The comment states "Exactly one of S3, PersistentVolumeClaim or Volume must be set," but there's no kubebuilder validation enforcing this constraint. Consider adding a CEL validation rule to catch configuration errors at admission time rather than during reconciliation.

🔍 Suggested kubebuilder validation

Add a validation rule to MariaDBStorageTemplate:

// MariaDBStorageTemplate mirrors k8s.mariadb.com/v1alpha1 Backup.spec.storage.
// Exactly one of S3, PersistentVolumeClaim or Volume must be set.
// +kubebuilder:validation:XValidation:rule="[has(self.s3), has(self.persistentVolumeClaim), has(self.volume)].filter(x, x).size() == 1",message="exactly one of s3, persistentVolumeClaim, or volume must be set"
type MariaDBStorageTemplate struct {

This CEL rule counts the number of non-null storage fields and ensures exactly one is present.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/backups/strategy/v1alpha1/mariadb_types.go` around lines 101 - 119, Add a
CEL XValidation kubebuilder tag on the MariaDBStorageTemplate type to enforce
that exactly one of the storage fields is set: S3, PersistentVolumeClaim or
Volume; update the type comment and add a +kubebuilder:validation:XValidation
annotation on MariaDBStorageTemplate that evaluates the presence of self.s3,
self.persistentVolumeClaim and self.volume and asserts their count equals 1 so
the admission webhook rejects CRs with zero or multiple storage fields set.
internal/backupcontroller/foundationdbstrategy_controller.go (1)

600-610: ⚡ Quick win

Avoid silently swallowing snapshot decode errors.

When decodeFoundationDBBackupSnapshot returns a real json.Unmarshal failure (i.e. underlyingResources.Raw is malformed, not just a foreign Kind), the error is discarded and the function silently degrades to the driverMetadata fallback. If the snapshot path was the only source carrying e.g. CustomParameters / EncryptionKeyPath, the restore proceeds with partial config and no breadcrumb tying the surprise back to a bad payload. Same pattern the MariaDB driver uses for MalformedOptions would help: log + event, then fall through.

♻️ Suggested change
-	snap, _ := decodeFoundationDBBackupSnapshot(backup.Status.UnderlyingResources)
+	snap, err := decodeFoundationDBBackupSnapshot(backup.Status.UnderlyingResources)
+	if err != nil {
+		logger.Info("malformed Backup.status.underlyingResources; falling back to driverMetadata", "error", err)
+		r.Recorder.Eventf(restoreJob, corev1.EventTypeWarning, "MalformedSnapshot",
+			"Backup.status.underlyingResources is not a valid FoundationDB snapshot; falling back to driverMetadata: %v", err)
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/backupcontroller/foundationdbstrategy_controller.go` around lines
600 - 610, Capture and handle the error returned by
decodeFoundationDBBackupSnapshot instead of discarding it: change the call to
receive an err (snap, err := decodeFoundationDBBackupSnapshot(...)); if err !=
nil then log the error via the controller logger and emit a Kubernetes event on
the restoreJob (similar to the MariaDB MalformedOptions pattern) to record the
malformed snapshot payload, then continue to fall back to
resolveFoundationDBRestoreBlob as before; reference
decodeFoundationDBBackupSnapshot, resolveFoundationDBRestoreBlob and the
restoreJob/event/logging helpers so reviewers can find and follow the change.
internal/backupcontroller/mariadbstrategy_controller.go (1)

30-32: 💤 Low value

Unused constant mariadbFieldManager.

Declared but not referenced anywhere in the package; golangci-lint flags it. Either wire it into the operator-side Backup / Restore Create/Patch calls as a server-side apply field manager, or drop the constant until it's actually needed.

♻️ If unused, remove
-	mariadbFieldManager = "cozystack-mariadb-backup-driver"
-
 	// mariadb-operator's MariaDB ApplicationDefinition (release.prefix). The
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/backupcontroller/mariadbstrategy_controller.go` around lines 30 -
32, The const mariadbFieldManager is declared but unused; either remove it or
wire it into server-side apply options for Backup/Restore operations: locate the
operator-side Backup and Restore create/patch calls (e.g., places calling
client.Create or client.Patch for Backup/Restore resources) and pass the field
owner option using mariadbFieldManager (e.g.,
client.FieldOwner(mariadbFieldManager) or the equivalent Create/Patch option
your controller-runtime version supports); if you choose not to use it now,
delete the mariadbFieldManager constant to satisfy the linter.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/backups/foundationdb/01-create-strategy.sh`:
- Around line 65-66: Replace the securityContext entry that sets runAsUser: 0
with runAsUser: 4059 for the backup container so it runs as the non-root
FoundationDB UID; update the same change for the template using backup_agent
(the YAML block containing securityContext and runAsUser) to match the
operator/main DB UID 4059 rather than root. Ensure there are no other
conflicting securityContext/runAsUser entries for the backup container and keep
any existing capabilities/volume mounts intact.

In `@examples/backups/foundationdb/90-scenario-admin-prepare.md`:
- Around line 31-34: Update the fenced code block containing the kubectl
commands so it includes a language specifier (e.g., change the opening ``` to
```bash) to enable proper syntax highlighting for the two lines "kubectl get
foundationdbs.strategy.backups.cozystack.io" and "kubectl get
backupclasses.backups.cozystack.io foundationdb-default"; simply add the
language token on the existing opening fence for that block.

In `@examples/backups/foundationdb/91-scenario-user-backup.md`:
- Around line 50-55: The fenced code block showing kubectl commands is missing a
language specifier; update the block delimiter to include a shell language
(e.g., "bash" or "shell") so tools render syntax highlighting and accessibility
correctly—modify the fenced block around the kubectl commands in the
91-scenario-user-backup snippet to start with ```bash (or ```shell) instead of
just ```, leaving the command lines (kubectl -n <ns> get
backupjobs.backups.cozystack.io <name> -o yaml, kubectl -n <ns> get
backups.backups.cozystack.io <name> -o yaml, kubectl -n <ns> get
foundationdbbackups.apps.foundationdb.org -l
backups.cozystack.io/owned-by.BackupJobName=<name>) unchanged.

In `@examples/backups/foundationdb/92-scenario-user-restore.md`:
- Around line 44-48: The fenced code block containing the kubectl commands
starting with "kubectl -n <ns> get restorejobs.backups.cozystack.io <name> -o
yaml" and the follow-up "kubectl -n <ns> get
foundationdbrestores.apps.foundationdb.org ..." should declare a language for
markdown linting; replace the opening triple backticks with an annotated fence
(e.g., ```bash) so the block becomes a bash code block.

In `@internal/backupcontroller/mariadbstrategy_controller.go`:
- Around line 751-796: The error text in buildMariaDBBackupStorage promises
"exactly one" but the switch allows the first non-nil of in.S3,
in.PersistentVolumeClaim, in.Volume to win, so update the function to enforce
exclusivity: before the switch (or instead of it) count the non-nil fields
(in.S3, in.PersistentVolumeClaim, in.Volume) and return an error if count != 1
with the same user-facing message; then proceed to construct the appropriate
mariadbtypes.BackupStorage for the single non-nil field. This ensures
buildMariaDBBackupStorage enforces the one-of contract rather than silently
preferring the first set field.

---

Nitpick comments:
In `@api/backups/strategy/v1alpha1/mariadb_types.go`:
- Around line 101-119: Add a CEL XValidation kubebuilder tag on the
MariaDBStorageTemplate type to enforce that exactly one of the storage fields is
set: S3, PersistentVolumeClaim or Volume; update the type comment and add a
+kubebuilder:validation:XValidation annotation on MariaDBStorageTemplate that
evaluates the presence of self.s3, self.persistentVolumeClaim and self.volume
and asserts their count equals 1 so the admission webhook rejects CRs with zero
or multiple storage fields set.

In `@internal/backupcontroller/foundationdbstrategy_controller.go`:
- Around line 600-610: Capture and handle the error returned by
decodeFoundationDBBackupSnapshot instead of discarding it: change the call to
receive an err (snap, err := decodeFoundationDBBackupSnapshot(...)); if err !=
nil then log the error via the controller logger and emit a Kubernetes event on
the restoreJob (similar to the MariaDB MalformedOptions pattern) to record the
malformed snapshot payload, then continue to fall back to
resolveFoundationDBRestoreBlob as before; reference
decodeFoundationDBBackupSnapshot, resolveFoundationDBRestoreBlob and the
restoreJob/event/logging helpers so reviewers can find and follow the change.

In `@internal/backupcontroller/mariadbstrategy_controller.go`:
- Around line 30-32: The const mariadbFieldManager is declared but unused;
either remove it or wire it into server-side apply options for Backup/Restore
operations: locate the operator-side Backup and Restore create/patch calls
(e.g., places calling client.Create or client.Patch for Backup/Restore
resources) and pass the field owner option using mariadbFieldManager (e.g.,
client.FieldOwner(mariadbFieldManager) or the equivalent Create/Patch option
your controller-runtime version supports); if you choose not to use it now,
delete the mariadbFieldManager constant to satisfy the linter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 022f2364-9592-45d2-841e-66af66a141eb

📥 Commits

Reviewing files that changed from the base of the PR and between 86f5267 and f960c39.

📒 Files selected for processing (46)
  • api/apps/v1alpha1/foundationdb/types.go
  • api/apps/v1alpha1/mariadb/types.go
  • api/backups/strategy/v1alpha1/foundationdb_types.go
  • api/backups/strategy/v1alpha1/mariadb_types.go
  • api/backups/strategy/v1alpha1/zz_generated.deepcopy.go
  • cmd/backupstrategy-controller/main.go
  • examples/backups/foundationdb/00-helpers.sh
  • examples/backups/foundationdb/01-create-strategy.sh
  • examples/backups/foundationdb/02-create-backupclass.sh
  • examples/backups/foundationdb/03-create-bucket.sh
  • examples/backups/foundationdb/04-create-foundationdb-src.sh
  • examples/backups/foundationdb/05-create-backupjob.sh
  • examples/backups/foundationdb/06-restore-in-place.sh
  • examples/backups/foundationdb/07-restore-to-copy.sh
  • examples/backups/foundationdb/90-scenario-admin-prepare.md
  • examples/backups/foundationdb/91-scenario-user-backup.md
  • examples/backups/foundationdb/92-scenario-user-restore.md
  • examples/backups/foundationdb/README.md
  • examples/backups/foundationdb/cleanup.sh
  • examples/backups/foundationdb/run-all.sh
  • internal/backupcontroller/backupjob_controller.go
  • internal/backupcontroller/backupjob_controller_test.go
  • internal/backupcontroller/foundationdbapp/types.go
  • internal/backupcontroller/foundationdbapp/zz_generated.deepcopy.go
  • internal/backupcontroller/foundationdbstrategy_controller.go
  • internal/backupcontroller/foundationdbstrategy_controller_test.go
  • internal/backupcontroller/foundationdbtypes/types.go
  • internal/backupcontroller/foundationdbtypes/zz_generated.deepcopy.go
  • internal/backupcontroller/mariadbapp/types.go
  • internal/backupcontroller/mariadbapp/zz_generated.deepcopy.go
  • internal/backupcontroller/mariadbstrategy_controller.go
  • internal/backupcontroller/mariadbstrategy_controller_test.go
  • internal/backupcontroller/mariadbtypes/types.go
  • internal/backupcontroller/mariadbtypes/zz_generated.deepcopy.go
  • internal/backupcontroller/restorejob_controller.go
  • internal/backupcontroller/restorejob_controller_test.go
  • packages/apps/foundationdb/README.md
  • packages/apps/foundationdb/values.schema.json
  • packages/apps/foundationdb/values.yaml
  • packages/apps/mariadb/README.md
  • packages/apps/mariadb/values.schema.json
  • packages/apps/mariadb/values.yaml
  • packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_foundationdbs.yaml
  • packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_mariadbs.yaml
  • packages/system/backupstrategy-controller/templates/rbac.yaml
  • packages/system/mariadb-rd/cozyrds/mariadb.yaml

Comment thread examples/backups/foundationdb/01-create-strategy.sh Outdated
Comment thread examples/backups/foundationdb/90-scenario-admin-prepare.md Outdated
Comment thread examples/backups/foundationdb/91-scenario-user-backup.md Outdated
Comment thread examples/backups/foundationdb/92-scenario-user-restore.md Outdated
Comment thread internal/backupcontroller/mariadbstrategy_controller.go
@androndo
Andrey Kolkov (androndo) force-pushed the feat/foundationdb-backup branch 2 times, most recently from f3a9bc6 to bd3989c Compare May 12, 2026 07:38

@coderabbitai coderabbitai Bot left a comment

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.

♻️ Duplicate comments (3)
examples/backups/foundationdb/90-scenario-admin-prepare.md (1)

31-34: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language specifier to code block.

The fenced code block should specify bash or shell for proper syntax highlighting and accessibility.

📝 Proposed fix
-```
+```bash
 kubectl get foundationdbs.strategy.backups.cozystack.io
 kubectl get backupclasses.backups.cozystack.io foundationdb-default
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @examples/backups/foundationdb/90-scenario-admin-prepare.md around lines 31 -
34, The fenced code block containing the two kubectl commands is missing a
language specifier; update the opening fence to include bash (i.e., change tobash) for the block with the lines "kubectl get
foundationdbs.strategy.backups.cozystack.io" and "kubectl get
backupclasses.backups.cozystack.io foundationdb-default" so the snippet is
properly highlighted and accessible.


</details>

</blockquote></details>
<details>
<summary>examples/backups/foundationdb/91-scenario-user-backup.md (1)</summary><blockquote>

`50-55`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Add a language identifier to the fenced code block.**

The code block should specify `bash` as the language for proper syntax highlighting and markdown linting compliance.




<details>
<summary>📝 Proposed fix</summary>

```diff
-```
+```bash
 kubectl -n <ns> get backupjobs.backups.cozystack.io <name> -o yaml
 kubectl -n <ns> get backups.backups.cozystack.io      <name> -o yaml
 kubectl -n <ns> get foundationdbbackups.apps.foundationdb.org \
   -l backups.cozystack.io/owned-by.BackupJobName=<name>
 ```
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @examples/backups/foundationdb/91-scenario-user-backup.md around lines 50 -
55, Update the fenced code block containing the kubectl commands (the block
starting with "kubectl -n get backupjobs.backups.cozystack.io -o
yaml" and including the two following kubectl lines) to include the language
identifier by changing the opening triple backticks from tobash so the
block becomes a bash code block; leave the closing ``` unchanged.


</details>

</blockquote></details>
<details>
<summary>examples/backups/foundationdb/92-scenario-user-restore.md (1)</summary><blockquote>

`44-48`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Add a language identifier to the fenced code block.**

The code block should specify `bash` as the language for proper syntax highlighting and markdown linting compliance.




<details>
<summary>📝 Proposed fix</summary>

```diff
-```
+```bash
 kubectl -n <ns> get restorejobs.backups.cozystack.io <name> -o yaml
 kubectl -n <ns> get foundationdbrestores.apps.foundationdb.org \
   -l backups.cozystack.io/owned-by.BackupJobName=<name>
 ```
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @examples/backups/foundationdb/92-scenario-user-restore.md around lines 44 -
48, The fenced code block containing the two kubectl commands should include the
language identifier "bash" for proper syntax highlighting and markdown linting;
update the block that wraps the lines starting with "kubectl -n get
restorejobs.backups.cozystack.io -o yaml" and "kubectl -n get
foundationdbrestores.apps.foundationdb.org ..." so the opening fence is
"bash" instead of "".


</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>examples/backups/mariadb/05-mariadb-src.yaml (1)</summary><blockquote>

`28-28`: _⚡ Quick win_

**Consider adding a comment warning about the placeholder password.**

While `REPLACE_WITH_PASSWORD` is self-evident, adding an explicit comment reminding users to replace this with a secure password before deployment would improve clarity for those following the examples.




<details>
<summary>📝 Suggested enhancement</summary>

```diff
   users:
     app:
+      # IMPORTANT: Replace with a secure password before deploying
       password: REPLACE_WITH_PASSWORD
       maxUserConnections: 100
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/backups/mariadb/05-mariadb-src.yaml` at line 28, The YAML contains a
placeholder value "password: REPLACE_WITH_PASSWORD"; add an inline comment
immediately after this key (e.g., "password: REPLACE_WITH_PASSWORD  # TODO:
replace with a secure password before deployment") to warn users to replace the
placeholder with a secure password and avoid committing defaults to production.
```

</details>

</blockquote></details>
<details>
<summary>internal/backupcontroller/mariadbstrategy_controller.go (1)</summary><blockquote>

`31-31`: _💤 Low value_

**Remove unused constant or add comment explaining future intent.**

`mariadbFieldManager` is never referenced in the codebase. Either remove it to keep the code clean, or add a comment if it's reserved for future client-side apply operations.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/backupcontroller/mariadbstrategy_controller.go` at line 31, The
constant mariadbFieldManager is defined but never used; either remove the
declaration of mariadbFieldManager from mariadbstrategy_controller.go to avoid
dead code, or if you intend to reserve it for future client-side apply usage,
add a short comment above the mariadbFieldManager constant explaining its
intended purpose (e.g., "reserved for client-side apply fieldManager name") so
its presence is justified.
```

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In @examples/backups/foundationdb/90-scenario-admin-prepare.md:

  • Around line 31-34: The fenced code block containing the two kubectl commands
    is missing a language specifier; update the opening fence to include bash (i.e.,
    change tobash) for the block with the lines "kubectl get
    foundationdbs.strategy.backups.cozystack.io" and "kubectl get
    backupclasses.backups.cozystack.io foundationdb-default" so the snippet is
    properly highlighted and accessible.

In @examples/backups/foundationdb/91-scenario-user-backup.md:

  • Around line 50-55: Update the fenced code block containing the kubectl
    commands (the block starting with "kubectl -n get
    backupjobs.backups.cozystack.io -o yaml" and including the two following
    kubectl lines) to include the language identifier by changing the opening triple
    backticks from tobash so the block becomes a bash code block; leave the
    closing ``` unchanged.

In @examples/backups/foundationdb/92-scenario-user-restore.md:

  • Around line 44-48: The fenced code block containing the two kubectl commands
    should include the language identifier "bash" for proper syntax highlighting and
    markdown linting; update the block that wraps the lines starting with "kubectl
    -n get restorejobs.backups.cozystack.io -o yaml" and "kubectl -n
    get foundationdbrestores.apps.foundationdb.org ..." so the opening fence is
    "bash" instead of "".

Nitpick comments:
In @examples/backups/mariadb/05-mariadb-src.yaml:

  • Line 28: The YAML contains a placeholder value "password:
    REPLACE_WITH_PASSWORD"; add an inline comment immediately after this key (e.g.,
    "password: REPLACE_WITH_PASSWORD # TODO: replace with a secure password before
    deployment") to warn users to replace the placeholder with a secure password and
    avoid committing defaults to production.

In @internal/backupcontroller/mariadbstrategy_controller.go:

  • Line 31: The constant mariadbFieldManager is defined but never used; either
    remove the declaration of mariadbFieldManager from mariadbstrategy_controller.go
    to avoid dead code, or if you intend to reserve it for future client-side apply
    usage, add a short comment above the mariadbFieldManager constant explaining its
    intended purpose (e.g., "reserved for client-side apply fieldManager name") so
    its presence is justified.

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: Path: .coderabbit.yaml

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `4ee45ca0-3af2-49b6-80a5-99bf041eb699`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between f960c397ad17283cf4c0199059f6d2b0c6cee173 and bd3989cf1e8d84d85ce379f07274d8d9999220ce.

</details>

<details>
<summary>📒 Files selected for processing (56)</summary>

* `api/apps/v1alpha1/foundationdb/types.go`
* `api/apps/v1alpha1/mariadb/types.go`
* `api/backups/strategy/v1alpha1/foundationdb_types.go`
* `api/backups/strategy/v1alpha1/mariadb_types.go`
* `api/backups/strategy/v1alpha1/zz_generated.deepcopy.go`
* `cmd/backupstrategy-controller/main.go`
* `examples/backups/foundationdb/00-helpers.sh`
* `examples/backups/foundationdb/01-create-strategy.sh`
* `examples/backups/foundationdb/02-create-backupclass.sh`
* `examples/backups/foundationdb/03-create-bucket.sh`
* `examples/backups/foundationdb/04-create-foundationdb-src.sh`
* `examples/backups/foundationdb/05-create-backupjob.sh`
* `examples/backups/foundationdb/06-restore-in-place.sh`
* `examples/backups/foundationdb/07-restore-to-copy.sh`
* `examples/backups/foundationdb/90-scenario-admin-prepare.md`
* `examples/backups/foundationdb/91-scenario-user-backup.md`
* `examples/backups/foundationdb/92-scenario-user-restore.md`
* `examples/backups/foundationdb/README.md`
* `examples/backups/foundationdb/cleanup.sh`
* `examples/backups/foundationdb/run-all.sh`
* `examples/backups/mariadb/00-bucket.yaml`
* `examples/backups/mariadb/05-mariadb-src.yaml`
* `examples/backups/mariadb/10-mariadb-strategy.yaml`
* `examples/backups/mariadb/15-backupclass.yaml`
* `examples/backups/mariadb/20-plan.yaml`
* `examples/backups/mariadb/25-backupjob-adhoc.yaml`
* `examples/backups/mariadb/30-mariadb-target.yaml`
* `examples/backups/mariadb/35-restorejob-in-place.yaml`
* `examples/backups/mariadb/40-restorejob-to-copy.yaml`
* `examples/backups/mariadb/README.md`
* `internal/backupcontroller/backupjob_controller.go`
* `internal/backupcontroller/backupjob_controller_test.go`
* `internal/backupcontroller/foundationdbapp/types.go`
* `internal/backupcontroller/foundationdbapp/zz_generated.deepcopy.go`
* `internal/backupcontroller/foundationdbstrategy_controller.go`
* `internal/backupcontroller/foundationdbstrategy_controller_test.go`
* `internal/backupcontroller/foundationdbtypes/types.go`
* `internal/backupcontroller/foundationdbtypes/zz_generated.deepcopy.go`
* `internal/backupcontroller/mariadbapp/types.go`
* `internal/backupcontroller/mariadbapp/zz_generated.deepcopy.go`
* `internal/backupcontroller/mariadbstrategy_controller.go`
* `internal/backupcontroller/mariadbstrategy_controller_test.go`
* `internal/backupcontroller/mariadbtypes/types.go`
* `internal/backupcontroller/mariadbtypes/zz_generated.deepcopy.go`
* `internal/backupcontroller/restorejob_controller.go`
* `internal/backupcontroller/restorejob_controller_test.go`
* `packages/apps/foundationdb/README.md`
* `packages/apps/foundationdb/values.schema.json`
* `packages/apps/foundationdb/values.yaml`
* `packages/apps/mariadb/README.md`
* `packages/apps/mariadb/values.schema.json`
* `packages/apps/mariadb/values.yaml`
* `packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_foundationdbs.yaml`
* `packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_mariadbs.yaml`
* `packages/system/backupstrategy-controller/templates/rbac.yaml`
* `packages/system/mariadb-rd/cozyrds/mariadb.yaml`

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (14)</summary>

* examples/backups/mariadb/00-bucket.yaml
* packages/apps/foundationdb/README.md
* examples/backups/mariadb/25-backupjob-adhoc.yaml
* examples/backups/mariadb/README.md
* packages/system/mariadb-rd/cozyrds/mariadb.yaml
* examples/backups/mariadb/30-mariadb-target.yaml
* packages/apps/mariadb/values.schema.json
* packages/apps/mariadb/README.md
* examples/backups/foundationdb/README.md
* internal/backupcontroller/mariadbapp/types.go
* api/apps/v1alpha1/foundationdb/types.go
* internal/backupcontroller/foundationdbapp/zz_generated.deepcopy.go
* api/backups/strategy/v1alpha1/zz_generated.deepcopy.go
* packages/apps/foundationdb/values.schema.json

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (17)</summary>

* packages/apps/foundationdb/values.yaml
* internal/backupcontroller/restorejob_controller.go
* examples/backups/foundationdb/run-all.sh
* cmd/backupstrategy-controller/main.go
* internal/backupcontroller/mariadbapp/zz_generated.deepcopy.go
* internal/backupcontroller/restorejob_controller_test.go
* internal/backupcontroller/backupjob_controller.go
* api/backups/strategy/v1alpha1/mariadb_types.go
* api/backups/strategy/v1alpha1/foundationdb_types.go
* examples/backups/foundationdb/00-helpers.sh
* internal/backupcontroller/backupjob_controller_test.go
* internal/backupcontroller/foundationdbtypes/types.go
* api/apps/v1alpha1/mariadb/types.go
* internal/backupcontroller/foundationdbapp/types.go
* internal/backupcontroller/foundationdbstrategy_controller_test.go
* internal/backupcontroller/foundationdbstrategy_controller.go
* internal/backupcontroller/foundationdbtypes/zz_generated.deepcopy.go

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
examples/backups/foundationdb/03-create-bucket.sh (1)

109-118: ⚡ Quick win

Serialize cached env values with shell-safe quoting.

These exports are written unquoted; special characters in any value can make .bucket-info.env unparsable when sourced later. Use shell-escaped output via printf %q.

Suggested patch
-umask 077
-cat > "$SCRIPT_DIR/.bucket-info.env" <<ENV
-export FDB_ACCESS_KEY=${FDB_ACCESS_KEY}
-export FDB_SECRET_KEY=${FDB_SECRET_KEY}
-export FDB_ENDPOINT_FULL=${FDB_ENDPOINT_FULL}
-export FDB_ENDPOINT_HOSTPORT=${FDB_ENDPOINT_HOSTPORT}
-export FDB_REGION=${FDB_REGION}
-export FDB_BUCKET=${FDB_BUCKET}
-export FDB_ACCOUNT_NAME=${FDB_ACCOUNT_NAME}
-export SECURE_CONNECTION=${SECURE_CONNECTION}
-ENV
+umask 077
+{
+    printf 'export FDB_ACCESS_KEY=%q\n' "$FDB_ACCESS_KEY"
+    printf 'export FDB_SECRET_KEY=%q\n' "$FDB_SECRET_KEY"
+    printf 'export FDB_ENDPOINT_FULL=%q\n' "$FDB_ENDPOINT_FULL"
+    printf 'export FDB_ENDPOINT_HOSTPORT=%q\n' "$FDB_ENDPOINT_HOSTPORT"
+    printf 'export FDB_REGION=%q\n' "$FDB_REGION"
+    printf 'export FDB_BUCKET=%q\n' "$FDB_BUCKET"
+    printf 'export FDB_ACCOUNT_NAME=%q\n' "$FDB_ACCOUNT_NAME"
+    printf 'export SECURE_CONNECTION=%q\n' "$SECURE_CONNECTION"
+} > "$SCRIPT_DIR/.bucket-info.env"
 chmod 600 "$SCRIPT_DIR/.bucket-info.env"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/backups/foundationdb/03-create-bucket.sh` around lines 109 - 118,
The here-doc writing to "$SCRIPT_DIR/.bucket-info.env" writes unquoted exports
(e.g., FDB_ACCESS_KEY, FDB_SECRET_KEY, FDB_ENDPOINT_FULL, etc.) which can break
when values contain spaces or special chars; change the creation to emit
shell-safe quoted values by printing each export using a shell-escaped formatter
(e.g., use printf with %q for each variable) so the file contains lines like
export VAR=<shell-escaped-value>, ensuring .bucket-info.env remains sourceable
and robust.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@examples/backups/foundationdb/03-create-bucket.sh`:
- Around line 109-118: The here-doc writing to "$SCRIPT_DIR/.bucket-info.env"
writes unquoted exports (e.g., FDB_ACCESS_KEY, FDB_SECRET_KEY,
FDB_ENDPOINT_FULL, etc.) which can break when values contain spaces or special
chars; change the creation to emit shell-safe quoted values by printing each
export using a shell-escaped formatter (e.g., use printf with %q for each
variable) so the file contains lines like export VAR=<shell-escaped-value>,
ensuring .bucket-info.env remains sourceable and robust.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 35f4e18b-f813-4616-ab78-93b37c79f283

📥 Commits

Reviewing files that changed from the base of the PR and between bd3989c and faf4410.

📒 Files selected for processing (58)
  • api/apps/v1alpha1/foundationdb/types.go
  • api/apps/v1alpha1/mariadb/types.go
  • api/backups/strategy/v1alpha1/foundationdb_types.go
  • api/backups/strategy/v1alpha1/mariadb_types.go
  • api/backups/strategy/v1alpha1/zz_generated.deepcopy.go
  • cmd/backupstrategy-controller/main.go
  • examples/backups/foundationdb/00-helpers.sh
  • examples/backups/foundationdb/01-create-strategy.sh
  • examples/backups/foundationdb/02-create-backupclass.sh
  • examples/backups/foundationdb/03-create-bucket.sh
  • examples/backups/foundationdb/04-create-foundationdb-src.sh
  • examples/backups/foundationdb/05-create-backupjob.sh
  • examples/backups/foundationdb/06-restore-in-place.sh
  • examples/backups/foundationdb/07-restore-to-copy.sh
  • examples/backups/foundationdb/90-scenario-admin-prepare.md
  • examples/backups/foundationdb/91-scenario-user-backup.md
  • examples/backups/foundationdb/92-scenario-user-restore.md
  • examples/backups/foundationdb/README.md
  • examples/backups/foundationdb/cleanup.sh
  • examples/backups/foundationdb/run-all.sh
  • examples/backups/mariadb/00-bucket.yaml
  • examples/backups/mariadb/05-mariadb-src.yaml
  • examples/backups/mariadb/10-mariadb-strategy.yaml
  • examples/backups/mariadb/15-backupclass.yaml
  • examples/backups/mariadb/20-plan.yaml
  • examples/backups/mariadb/25-backupjob-adhoc.yaml
  • examples/backups/mariadb/30-mariadb-target.yaml
  • examples/backups/mariadb/35-restorejob-in-place.yaml
  • examples/backups/mariadb/40-restorejob-to-copy.yaml
  • examples/backups/mariadb/README.md
  • internal/backupcontroller/backup_controller.go
  • internal/backupcontroller/backup_controller_test.go
  • internal/backupcontroller/backupjob_controller.go
  • internal/backupcontroller/backupjob_controller_test.go
  • internal/backupcontroller/foundationdbapp/types.go
  • internal/backupcontroller/foundationdbapp/zz_generated.deepcopy.go
  • internal/backupcontroller/foundationdbstrategy_controller.go
  • internal/backupcontroller/foundationdbstrategy_controller_test.go
  • internal/backupcontroller/foundationdbtypes/types.go
  • internal/backupcontroller/foundationdbtypes/zz_generated.deepcopy.go
  • internal/backupcontroller/mariadbapp/types.go
  • internal/backupcontroller/mariadbapp/zz_generated.deepcopy.go
  • internal/backupcontroller/mariadbstrategy_controller.go
  • internal/backupcontroller/mariadbstrategy_controller_test.go
  • internal/backupcontroller/mariadbtypes/types.go
  • internal/backupcontroller/mariadbtypes/zz_generated.deepcopy.go
  • internal/backupcontroller/restorejob_controller.go
  • internal/backupcontroller/restorejob_controller_test.go
  • packages/apps/foundationdb/README.md
  • packages/apps/foundationdb/values.schema.json
  • packages/apps/foundationdb/values.yaml
  • packages/apps/mariadb/README.md
  • packages/apps/mariadb/values.schema.json
  • packages/apps/mariadb/values.yaml
  • packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_foundationdbs.yaml
  • packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_mariadbs.yaml
  • packages/system/backupstrategy-controller/templates/rbac.yaml
  • packages/system/mariadb-rd/cozyrds/mariadb.yaml
✅ Files skipped from review due to trivial changes (17)
  • examples/backups/mariadb/20-plan.yaml
  • examples/backups/mariadb/README.md
  • packages/apps/mariadb/README.md
  • examples/backups/foundationdb/README.md
  • examples/backups/mariadb/40-restorejob-to-copy.yaml
  • packages/apps/foundationdb/values.yaml
  • packages/apps/foundationdb/README.md
  • examples/backups/foundationdb/run-all.sh
  • examples/backups/mariadb/15-backupclass.yaml
  • packages/system/mariadb-rd/cozyrds/mariadb.yaml
  • internal/backupcontroller/restorejob_controller.go
  • packages/apps/mariadb/values.schema.json
  • api/apps/v1alpha1/mariadb/types.go
  • packages/apps/foundationdb/values.schema.json
  • api/apps/v1alpha1/foundationdb/types.go
  • internal/backupcontroller/foundationdbtypes/zz_generated.deepcopy.go
  • api/backups/strategy/v1alpha1/zz_generated.deepcopy.go
🚧 Files skipped from review as they are similar to previous changes (22)
  • cmd/backupstrategy-controller/main.go
  • examples/backups/mariadb/05-mariadb-src.yaml
  • internal/backupcontroller/backupjob_controller.go
  • examples/backups/mariadb/30-mariadb-target.yaml
  • examples/backups/mariadb/10-mariadb-strategy.yaml
  • internal/backupcontroller/restorejob_controller_test.go
  • examples/backups/mariadb/35-restorejob-in-place.yaml
  • examples/backups/mariadb/25-backupjob-adhoc.yaml
  • internal/backupcontroller/foundationdbapp/types.go
  • internal/backupcontroller/foundationdbapp/zz_generated.deepcopy.go
  • internal/backupcontroller/backupjob_controller_test.go
  • internal/backupcontroller/mariadbapp/zz_generated.deepcopy.go
  • internal/backupcontroller/foundationdbstrategy_controller_test.go
  • api/backups/strategy/v1alpha1/mariadb_types.go
  • internal/backupcontroller/mariadbtypes/types.go
  • internal/backupcontroller/mariadbapp/types.go
  • examples/backups/foundationdb/00-helpers.sh
  • internal/backupcontroller/foundationdbtypes/types.go
  • internal/backupcontroller/mariadbtypes/zz_generated.deepcopy.go
  • internal/backupcontroller/foundationdbstrategy_controller.go
  • internal/backupcontroller/mariadbstrategy_controller_test.go
  • internal/backupcontroller/mariadbstrategy_controller.go

@androndo
Andrey Kolkov (androndo) force-pushed the feat/foundationdb-backup branch 2 times, most recently from 90da70d to 6d80ec0 Compare May 12, 2026 13:57

@coderabbitai coderabbitai Bot left a comment

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.

♻️ Duplicate comments (2)
examples/backups/foundationdb/91-scenario-user-backup.md (1)

50-55: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language specifier to fenced code block.

This issue was previously flagged and remains unaddressed. The code block should specify bash for proper syntax highlighting and accessibility.

📝 Proposed fix
-```
+```bash
 kubectl -n <ns> get backupjobs.backups.cozystack.io <name> -o yaml
 kubectl -n <ns> get backups.backups.cozystack.io      <name> -o yaml
 kubectl -n <ns> get foundationdbbackups.apps.foundationdb.org \
   -l backups.cozystack.io/owned-by.BackupJobName=<name>
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @examples/backups/foundationdb/91-scenario-user-backup.md around lines 50 -
55, The fenced code block containing the kubectl examples (the triple-backtick
block that starts before "kubectl -n get backupjobs.backups.cozystack.io
-o yaml" and ends after the foundationdbbackups command) needs a language
specifier; change the opening fence from tobash so the block is marked
as bash for proper syntax highlighting and accessibility while leaving the
kubectl command lines unchanged.


</details>

</blockquote></details>
<details>
<summary>examples/backups/foundationdb/92-scenario-user-restore.md (1)</summary><blockquote>

`44-48`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Add a language identifier to the fenced code block.**

This issue was previously flagged and remains unaddressed. The code block should specify `bash` to improve readability and satisfy markdown lint.





<details>
<summary>📝 Proposed fix</summary>

```diff
-```
+```bash
 kubectl -n <ns> get restorejobs.backups.cozystack.io <name> -o yaml
 kubectl -n <ns> get foundationdbrestores.apps.foundationdb.org \
   -l backups.cozystack.io/owned-by.BackupJobName=<name>
 ```
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @examples/backups/foundationdb/92-scenario-user-restore.md around lines 44 -
48, The fenced code block containing the kubectl commands should include a
language identifier to enable syntax highlighting; change the opening fence from
tobash for the block that starts with "kubectl -n get
restorejobs.backups.cozystack.io -o yaml" (the block that also contains
the "kubectl -n get foundationdbrestores.apps.foundationdb.org" command) so
the markdown linter and readers recognize it as bash.


</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In @examples/backups/foundationdb/91-scenario-user-backup.md:

  • Around line 50-55: The fenced code block containing the kubectl examples (the
    triple-backtick block that starts before "kubectl -n get
    backupjobs.backups.cozystack.io -o yaml" and ends after the
    foundationdbbackups command) needs a language specifier; change the opening
    fence from tobash so the block is marked as bash for proper syntax
    highlighting and accessibility while leaving the kubectl command lines
    unchanged.

In @examples/backups/foundationdb/92-scenario-user-restore.md:

  • Around line 44-48: The fenced code block containing the kubectl commands
    should include a language identifier to enable syntax highlighting; change the
    opening fence from tobash for the block that starts with "kubectl -n
    get restorejobs.backups.cozystack.io -o yaml" (the block that also
    contains the "kubectl -n get foundationdbrestores.apps.foundationdb.org"
    command) so the markdown linter and readers recognize it as bash.

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: Path: .coderabbit.yaml

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `417bafe1-fc1c-469a-8cac-8132d5e3441a`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between faf44101e7862d5d7975be4b8a1ce5c748b81fc2 and 90da70dfb1b3d4e5cedaebd185efbcd69e4b475a.

</details>

<details>
<summary>📒 Files selected for processing (33)</summary>

* `api/apps/v1alpha1/foundationdb/types.go`
* `api/backups/strategy/v1alpha1/foundationdb_types.go`
* `api/backups/strategy/v1alpha1/zz_generated.deepcopy.go`
* `cmd/backupstrategy-controller/main.go`
* `examples/backups/foundationdb/00-helpers.sh`
* `examples/backups/foundationdb/01-create-strategy.sh`
* `examples/backups/foundationdb/02-create-backupclass.sh`
* `examples/backups/foundationdb/03-create-bucket.sh`
* `examples/backups/foundationdb/04-create-foundationdb-src.sh`
* `examples/backups/foundationdb/05-create-backupjob.sh`
* `examples/backups/foundationdb/06-restore-in-place.sh`
* `examples/backups/foundationdb/07-restore-to-copy.sh`
* `examples/backups/foundationdb/90-scenario-admin-prepare.md`
* `examples/backups/foundationdb/91-scenario-user-backup.md`
* `examples/backups/foundationdb/92-scenario-user-restore.md`
* `examples/backups/foundationdb/README.md`
* `examples/backups/foundationdb/cleanup.sh`
* `examples/backups/foundationdb/run-all.sh`
* `internal/backupcontroller/backupjob_controller.go`
* `internal/backupcontroller/backupjob_controller_test.go`
* `internal/backupcontroller/foundationdbapp/types.go`
* `internal/backupcontroller/foundationdbapp/zz_generated.deepcopy.go`
* `internal/backupcontroller/foundationdbstrategy_controller.go`
* `internal/backupcontroller/foundationdbstrategy_controller_test.go`
* `internal/backupcontroller/foundationdbtypes/types.go`
* `internal/backupcontroller/foundationdbtypes/zz_generated.deepcopy.go`
* `internal/backupcontroller/restorejob_controller.go`
* `internal/backupcontroller/restorejob_controller_test.go`
* `packages/apps/foundationdb/README.md`
* `packages/apps/foundationdb/values.schema.json`
* `packages/apps/foundationdb/values.yaml`
* `packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_foundationdbs.yaml`
* `packages/system/backupstrategy-controller/templates/rbac.yaml`

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (5)</summary>

* packages/apps/foundationdb/README.md
* packages/apps/foundationdb/values.schema.json
* examples/backups/foundationdb/README.md
* api/apps/v1alpha1/foundationdb/types.go
* api/backups/strategy/v1alpha1/zz_generated.deepcopy.go

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (13)</summary>

* examples/backups/foundationdb/run-all.sh
* internal/backupcontroller/restorejob_controller.go
* internal/backupcontroller/foundationdbapp/types.go
* internal/backupcontroller/backupjob_controller_test.go
* cmd/backupstrategy-controller/main.go
* internal/backupcontroller/foundationdbtypes/types.go
* api/backups/strategy/v1alpha1/foundationdb_types.go
* examples/backups/foundationdb/00-helpers.sh
* internal/backupcontroller/foundationdbapp/zz_generated.deepcopy.go
* internal/backupcontroller/restorejob_controller_test.go
* internal/backupcontroller/foundationdbtypes/zz_generated.deepcopy.go
* internal/backupcontroller/foundationdbstrategy_controller_test.go
* internal/backupcontroller/foundationdbstrategy_controller.go

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

@coderabbitai coderabbitai Bot left a comment

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.

♻️ Duplicate comments (2)
examples/backups/foundationdb/91-scenario-user-backup.md (1)

50-55: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language specifier to fenced code block.

The code block should specify bash or shell as the language for proper syntax highlighting and better accessibility.

📝 Proposed fix
-```
+```bash
 kubectl -n <ns> get backupjobs.backups.cozystack.io <name> -o yaml
 kubectl -n <ns> get backups.backups.cozystack.io      <name> -o yaml
 kubectl -n <ns> get foundationdbbackups.apps.foundationdb.org \
   -l backups.cozystack.io/owned-by.BackupJobName=<name>
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @examples/backups/foundationdb/91-scenario-user-backup.md around lines 50 -
55, The fenced code block containing the three kubectl commands (the lines
beginning with "kubectl -n get backupjobs.backups.cozystack.io", "kubectl
-n get backups.backups.cozystack.io", and "kubectl -n get
foundationdbbackups.apps.foundationdb.org") needs a language specifier for
syntax highlighting; update the opening fence from tobash (or ```shell)
so the block is marked as bash/shell.


</details>

</blockquote></details>
<details>
<summary>examples/backups/foundationdb/90-scenario-admin-prepare.md (1)</summary><blockquote>

`31-34`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Add language specifier to fenced code block.**

The code block should specify `bash` or `shell` as the language for proper syntax highlighting and better accessibility.




<details>
<summary>📝 Proposed fix</summary>

```diff
-```
+```bash
 kubectl get foundationdbs.strategy.backups.cozystack.io
 kubectl get backupclasses.backups.cozystack.io foundationdb-default
 ```
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @examples/backups/foundationdb/90-scenario-admin-prepare.md around lines 31 -
34, The fenced code block containing the kubectl commands in
examples/backups/foundationdb/90-scenario-admin-prepare.md should include a
language specifier (e.g., bash or shell) for proper syntax highlighting; update
the triple-backtick fence that wraps "kubectl get
foundationdbs.strategy.backups.cozystack.io" and "kubectl get
backupclasses.backups.cozystack.io foundationdb-default" to use bash (or shell) at the start and close with ``` so the block is annotated for
highlighting and accessibility.


</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>internal/backupcontroller/foundationdbtypes/zz_generated.deepcopy.go (1)</summary><blockquote>

`175-187`: _💤 Low value_

**Consider adding standalone DeepCopy methods for consistency.**

`BackupDeploymentSpec` and `BlobStoreConfiguration` only implement `DeepCopyInto`, unlike other types in this file that provide both `DeepCopyInto` and `DeepCopy`. While not strictly required if these are only used as embedded fields, adding standalone `DeepCopy` methods would improve consistency and future flexibility.




<details>
<summary>♻️ Example implementation for consistency</summary>

```diff
 func (in *BackupDeploymentSpec) DeepCopyInto(out *BackupDeploymentSpec) {
 	*out = *in
 	if in.PodTemplateSpec != nil {
 		out.PodTemplateSpec = in.PodTemplateSpec.DeepCopy()
 	}
 }
+
+func (in *BackupDeploymentSpec) DeepCopy() *BackupDeploymentSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(BackupDeploymentSpec)
+	in.DeepCopyInto(out)
+	return out
+}

 func (in *BlobStoreConfiguration) DeepCopyInto(out *BlobStoreConfiguration) {
 	*out = *in
 	if in.URLParameters != nil {
 		out.URLParameters = append([]string(nil), in.URLParameters...)
 	}
 }
+
+func (in *BlobStoreConfiguration) DeepCopy() *BlobStoreConfiguration {
+	if in == nil {
+		return nil
+	}
+	out := new(BlobStoreConfiguration)
+	in.DeepCopyInto(out)
+	return out
+}
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/backupcontroller/foundationdbtypes/zz_generated.deepcopy.go` around
lines 175 - 187, Add missing standalone DeepCopy methods for
BackupDeploymentSpec and BlobStoreConfiguration to match other types: implement
BackupDeploymentSpec.DeepCopy() *BackupDeploymentSpec and
BlobStoreConfiguration.DeepCopy() *BlobStoreConfiguration that return nil if the
receiver is nil, otherwise allocate a new value, call the existing
DeepCopyInto(out) to populate it, and return the new pointer; reference the
existing DeepCopyInto implementations for PodTemplateSpec and URLParameters
copying behavior when writing these methods.
```

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In @examples/backups/foundationdb/90-scenario-admin-prepare.md:

  • Around line 31-34: The fenced code block containing the kubectl commands in
    examples/backups/foundationdb/90-scenario-admin-prepare.md should include a
    language specifier (e.g., bash or shell) for proper syntax highlighting; update
    the triple-backtick fence that wraps "kubectl get
    foundationdbs.strategy.backups.cozystack.io" and "kubectl get
    backupclasses.backups.cozystack.io foundationdb-default" to use bash (or shell) at the start and close with ``` so the block is annotated for
    highlighting and accessibility.

In @examples/backups/foundationdb/91-scenario-user-backup.md:

  • Around line 50-55: The fenced code block containing the three kubectl commands
    (the lines beginning with "kubectl -n get backupjobs.backups.cozystack.io",
    "kubectl -n get backups.backups.cozystack.io", and "kubectl -n get
    foundationdbbackups.apps.foundationdb.org") needs a language specifier for
    syntax highlighting; update the opening fence from tobash (or ```shell)
    so the block is marked as bash/shell.

Nitpick comments:
In @internal/backupcontroller/foundationdbtypes/zz_generated.deepcopy.go:

  • Around line 175-187: Add missing standalone DeepCopy methods for
    BackupDeploymentSpec and BlobStoreConfiguration to match other types: implement
    BackupDeploymentSpec.DeepCopy() *BackupDeploymentSpec and
    BlobStoreConfiguration.DeepCopy() *BlobStoreConfiguration that return nil if the
    receiver is nil, otherwise allocate a new value, call the existing
    DeepCopyInto(out) to populate it, and return the new pointer; reference the
    existing DeepCopyInto implementations for PodTemplateSpec and URLParameters
    copying behavior when writing these methods.

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: Path: .coderabbit.yaml

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `ffc131ab-ffa9-4704-8c93-ba615ca90741`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 90da70dfb1b3d4e5cedaebd185efbcd69e4b475a and 6d80ec07be6da3c4728f9141dc3e1faa4a90b0da.

</details>

<details>
<summary>📒 Files selected for processing (34)</summary>

* `api/apps/v1alpha1/foundationdb/types.go`
* `api/backups/strategy/v1alpha1/foundationdb_types.go`
* `api/backups/strategy/v1alpha1/zz_generated.deepcopy.go`
* `cmd/backupstrategy-controller/main.go`
* `examples/backups/foundationdb/00-helpers.sh`
* `examples/backups/foundationdb/01-create-strategy.sh`
* `examples/backups/foundationdb/02-create-backupclass.sh`
* `examples/backups/foundationdb/03-create-bucket.sh`
* `examples/backups/foundationdb/04-create-foundationdb-src.sh`
* `examples/backups/foundationdb/05-create-backupjob.sh`
* `examples/backups/foundationdb/06-restore-in-place.sh`
* `examples/backups/foundationdb/07-restore-to-copy.sh`
* `examples/backups/foundationdb/90-scenario-admin-prepare.md`
* `examples/backups/foundationdb/91-scenario-user-backup.md`
* `examples/backups/foundationdb/92-scenario-user-restore.md`
* `examples/backups/foundationdb/README.md`
* `examples/backups/foundationdb/cleanup.sh`
* `examples/backups/foundationdb/run-all.sh`
* `internal/backupcontroller/backupjob_controller.go`
* `internal/backupcontroller/backupjob_controller_test.go`
* `internal/backupcontroller/foundationdbapp/types.go`
* `internal/backupcontroller/foundationdbapp/zz_generated.deepcopy.go`
* `internal/backupcontroller/foundationdbstrategy_controller.go`
* `internal/backupcontroller/foundationdbstrategy_controller_test.go`
* `internal/backupcontroller/foundationdbtypes/types.go`
* `internal/backupcontroller/foundationdbtypes/zz_generated.deepcopy.go`
* `internal/backupcontroller/restorejob_controller.go`
* `internal/backupcontroller/restorejob_controller_test.go`
* `packages/apps/foundationdb/README.md`
* `packages/apps/foundationdb/values.schema.json`
* `packages/apps/foundationdb/values.yaml`
* `packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_foundationdbs.yaml`
* `packages/system/backupstrategy-controller/templates/rbac.yaml`
* `packages/system/foundationdb-rd/cozyrds/foundationdb.yaml`

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (6)</summary>

* packages/apps/foundationdb/values.schema.json
* packages/apps/foundationdb/README.md
* examples/backups/foundationdb/README.md
* packages/apps/foundationdb/values.yaml
* api/backups/strategy/v1alpha1/zz_generated.deepcopy.go
* packages/system/foundationdb-rd/cozyrds/foundationdb.yaml

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (12)</summary>

* api/apps/v1alpha1/foundationdb/types.go
* cmd/backupstrategy-controller/main.go
* examples/backups/foundationdb/00-helpers.sh
* examples/backups/foundationdb/run-all.sh
* internal/backupcontroller/restorejob_controller.go
* internal/backupcontroller/foundationdbapp/zz_generated.deepcopy.go
* internal/backupcontroller/backupjob_controller.go
* internal/backupcontroller/backupjob_controller_test.go
* internal/backupcontroller/foundationdbtypes/types.go
* internal/backupcontroller/foundationdbapp/types.go
* internal/backupcontroller/foundationdbstrategy_controller.go
* internal/backupcontroller/foundationdbstrategy_controller_test.go

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

@lllamnyp Timofei Larkin (lllamnyp) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review saved to: /tmp/branch-review-feat-foundationdb-backup-1778597123.md

NOT LGTM

Target branch: main (from PR #2606 base). Merge base: 5f83ae72a8ec (head of main after the MariaDB backup feature). Single commit on this branch (6f513663c feat(platform): add implementation of backup-strategy controller for foundationdb). All FoundationDB tests pass locally (go test ./internal/backupcontroller/ -count=1 clean), and go build ./... is clean. The work is large (~12.9k lines) but well-structured and mirrors the established CNPG / MariaDB driver patterns closely.

Worth calling out before the blockers: this is genuinely good work. The test surface is dense, the gates against operator-specific quirks (one-running-backup-per-cluster, in-chart-conflict diagnostic, blob-credentials precondition, deadline-expiry cleanup) are real and the test names map cleanly to review item numbers, suggesting prior review cycles already chewed through the obvious foot-guns. The blockers below are not architectural — they are contract-violation seams and one factual error in shipped documentation.

Blockers

1. decodeFoundationDBBackupSnapshot ignores the apiVersion it explicitly promises to check

internal/backupcontroller/foundationdbstrategy_controller.go:1063-1076 (decode), 1031-1043 (marshal), and the foundationdbBackupSnapshotKind constant block at internal/backupcontroller/foundationdbstrategy_controller.go:381-389 and 391-394 (apiVersion var).

The comment block on the snapshot constants says, verbatim:

Bump the apiVersion (or introduce a Version field on foundationdbBackupSnapshot) the moment the on-disk shape changes in a way that breaks older readers — decoders deployed against a mismatched apiVersion must be able to detect the mismatch and refuse to use the snapshot rather than silently mis-interpret it.

The decoder checks only snap.Kind != foundationdbBackupSnapshotKind and returns (nil, nil) for any other case. It never reads snap.APIVersion. The contract documented next to the constant is therefore unenforceable: a future v2 snapshot with the same Kind (the most likely shape of a "bump the apiVersion" change) will be silently parsed as v1, the v2 fields the v1 struct doesn't know about will be dropped, and a restore will run against a stale interpretation of the persisted strategy. The decode call site at internal/backupcontroller/foundationdbstrategy_controller.go:822 (snap, _ := decodeFoundationDBBackupSnapshot(...)) discards the error too, so even if the decoder grew an error return for version mismatch, the consumer would ignore it.

This is the classic comment-as-fix anti-pattern: the future risk is documented but the code does not honor the documentation. Either delete the warning (and the dangling foundationdbBackupSnapshotAPIVersion variable that nothing reads) or enforce it.

Fix: in decodeFoundationDBBackupSnapshot, after the Kind check, also reject snapshots whose APIVersion does not match foundationdbBackupSnapshotAPIVersion. Return a typed error (e.g. errSnapshotAPIVersionMismatch) so the call site at line 822 can distinguish "no snapshot present" (current behavior, fall through to driverMetadata) from "snapshot present but unreadable" (RestoreJob must fail terminally — falling back to driverMetadata on a known-mismatched snapshot would corrupt the restore). Update the line-822 call site to surface that terminal error via markRestoreJobFailed.

Test: add TestDecodeFoundationDBBackupSnapshot_RejectsMismatchedAPIVersion that marshals a snapshot with APIVersion: "future.cozystack.io/v2", decodes it, and asserts the decoder returns a non-nil error and a nil snapshot. Add TestReconcileFoundationDBRestore_SnapshotAPIVersionMismatchIsTerminal that seeds a Backup whose status.underlyingResources carries the future apiVersion, drives the restore reconcile, and asserts RestoreJob.status.phase == Failed with a Ready=False condition message that mentions the version mismatch (and that no FoundationDBRestore CR was created). Both fail without the fix.

2. The 92-scenario-user-restore.md verification command is factually wrong and will mislead users

examples/backups/foundationdb/92-scenario-user-restore.md:78-86:

kubectl -n <ns> get foundationdbrestores.apps.foundationdb.org \
  -l backups.cozystack.io/owned-by.BackupJobName=<name>

Note: the label key is named `owned-by.BackupJobName` for historical
reasons (it is shared with the BackupJob ownership label), but for
`FoundationDBRestore` CRs the value is the **RestoreJob** name, not a
BackupJob name. Use `<RESTOREJOB_INPLACE_NAME>` or
`<RESTOREJOB_TOCOPY_NAME>` when filtering for restore artefacts.

The label is correctly identified (api/backups/v1alpha1/backupjob_types.go:25 defines OwningJobNameLabel = thisGroup + "/owned-by.BackupJobName"), and the doc is right that the value on a FoundationDBRestore is the RestoreJob name. But the cleanup.sh you shipped alongside this doc uses the SAME label selector with the SAME key — and the script's loop iterates over RestoreJob names AND the BackupJob name as if they all live under the same label:

examples/backups/foundationdb/cleanup.sh:24-28:

for owner in "${BACKUPJOB_NAME}" "${RESTOREJOB_INPLACE_NAME}" "${RESTOREJOB_TOCOPY_NAME}"; do
    kubectl -n "$NAMESPACE" delete --ignore-not-found --wait=false \
        foundationdbbackups.apps.foundationdb.org,foundationdbrestores.apps.foundationdb.org \
        -l "backups.cozystack.io/owned-by.BackupJobName=${owner}" || true
done

That part is actually correct (the loop pretends BackupJob and RestoreJob names are interchangeable label values, which they are because both ensureFoundationDBBackup and ensureFoundationDBRestore set OwningJobNameLabel to their own owner's name). So cleanup.sh works.

But the doc carries a second factual error that cleanup.sh doesn't catch: the doc instructs users to filter FoundationDBRestore by BackupJob-named labels for verification ("BackupJobName=" with hint to substitute the RestoreJob name). For a user copy-pasting the example, that works mechanically. The real problem is the SECOND code block at 92-scenario-user-restore.md:78-81 says to get foundationdbrestores but the example in 91-scenario-user-backup.md:62-64 uses the IDENTICAL label-key selector to get foundationdbbackups — which means if a user has both BackupJob and RestoreJob CRs whose names collide (e.g. both set to "fdb-backup-1"), the verification command from either doc will list BOTH the operator-side Backup and the operator-side Restore. The docs do not warn about this, and the label key is genuinely shared.

This is fixable in the docs without code changes, and it's important because the example uses different names for the source BackupJob and the RestoreJobs so the demo coincidentally avoids the collision — but a real tenant who reuses names will hit a surprise.

Fix: in 92-scenario-user-restore.md, change the verification code block to:

kubectl -n <ns> get foundationdbrestores.apps.foundationdb.org \
  -l backups.cozystack.io/owned-by.BackupJobName=<restorejob-name> \
  -l backups.cozystack.io/owned-by.BackupJobNamespace=<ns>

and add a warning: "The label key is owned-by.BackupJobName regardless of whether the value names a BackupJob or a RestoreJob. If your BackupJob and RestoreJob share the same name, also filter by resource kind. Consider renaming the label in a future API revision."

In 91-scenario-user-backup.md, mirror the same warning.

Test: this is a documentation-accuracy blocker. The right way to guard it is NOT a test that parses the markdown files (per CLAUDE.md guidance against grep-the-doc tests); the right way is to make the label NAME unambiguous. Track that as a follow-up API task (backups.cozystack.io/owned-by.JobName + a kind-discriminating label). For THIS PR, the doc fix above is the deliverable. The existing TestEnsureFoundationDBRestore_IdempotentByLabel and TestEnsureFoundationDBBackup_IdempotentByLabel together already pin that both kinds use the same key — that's the underlying contract, and the doc must accurately describe it.

3. 02-create-backupclass.sh writes accountName: "REPLACE_ME" to the cluster and step 03 patches it; if step 03 fails or is skipped, a tenant who runs 05-create-backupjob.sh burns 45 wall-clock minutes before learning the strategy is half-configured

examples/backups/foundationdb/02-create-backupclass.sh:30-39 (writes REPLACE_ME placeholders), and examples/backups/foundationdb/05-create-backupjob.sh:31 (wait_for_field ... 2700 — 45-minute timeout matching the driver's foundationdbDefaultBackupDeadline).

The script's own comment acknowledges this is intentional:

Empty values cause the strategy to render an invalid FoundationDBBackup (accountName is required), which surfaces as a BackupJob Failed condition - intentional, so a half-configured tenant fails fast.

But accountName: "REPLACE_ME" is NOT empty. It's a non-empty 10-character string that passes the +kubebuilder:validation:MinLength=1 validation on api/backups/strategy/v1alpha1/foundationdb_types.go:177. The driver renders the strategy successfully (no template error, the value is a literal), creates the FoundationDBBackup CR successfully (operator-side OpenAPI accepts any non-empty accountName), and the operator's backup_agent Deployment crash-loops trying to dial REPLACE_ME@<unknown-host>. The foundationdbDefaultBackupDeadline is 45 minutes (internal/backupcontroller/foundationdbstrategy_controller.go:415); the BackupJob spins for the full deadline before terminating.

The comment is wrong about the failure mode (it claims operator-side validation rejects accountName when it's empty; the kubebuilder validation actually rejects empty accountName, not literal REPLACE_ME), so the "fail fast" claim only holds if a tenant actually managed to apply step 02 in a way that produced an empty value — and the YAML they paste contains the literal string REPLACE_ME, so they can't.

Fix (pick one):

  • (preferred) Remove step 02 from the script set entirely and fold its body into step 03's "patch BackupClass parameters with resolved blob-store config" apply at 03-create-bucket.sh:80. Step 03 already does a full apply with the resolved values; there is no functional reason for step 02 to apply a placeholder version of the same resource first.
  • (alternative) In step 02, leave the BackupClass with NO strategies block at all (just spec: {} or spec: { strategies: [] }), and have step 03 add the strategy entry. An empty strategies list is invalid per the framework (a BackupJob will Fail with no strategy matches), but it Fails fast on the first reconcile rather than on the 45-minute deadline.
  • (last-resort) Tighten the strategy CRD's validation: forbid the literal sentinel string. Bad option — sentinels are demo-script noise, not API constraints.

Test: add an integration-style unit test TestReconcileFoundationDB_RejectsPlaceholderAccountName only if you pick the "tighten validation" path (validate that accountName="REPLACE_ME" results in a terminal BackupJob Failed within the first reconcile). For the "fold steps 02 and 03" path, no Go test — the change is in the shell-script demo; rerun run-all.sh end-to-end on a real cluster and confirm step 05 still works AND skipping step 03 produces a kubectl apply error rather than a 45-minute spin. The e2e run IS the test here, per the project's stance against parsing-shell-scripts-from-Go.

4. j.Status.Phase = Running Status Update at foundationdbstrategy_controller.go:246-251 runs AFTER ensureFoundationDBBackup has already created a server-side CR; on Status().Update conflict the BackupJob is wedged with an orphan FoundationDBBackup whose label matches a BackupJob whose status was never advanced past Pending

internal/backupcontroller/foundationdbstrategy_controller.go:246-251:

if j.Status.Phase != backupsv1alpha1.BackupJobPhaseRunning {
    j.Status.Phase = backupsv1alpha1.BackupJobPhaseRunning
    if err := r.Status().Update(ctx, j); err != nil {
        return ctrl.Result{}, err
    }
}

The Update returns the bare error, which controller-runtime reschedules. On the next reconcile, findFoundationDBBackupForJob matches the orphan CR (correct), j.Status.Phase is still empty/Pending (because the previous Status update lost), the if-block re-enters, the Status Update may succeed this time — fine. But the ordering is fragile: between the Create on line 137 (inside ensureFoundationDBBackup) and the Status Update on 248, any uncached condition on the parent BackupJob (e.g. concurrent deletion that flips it to terminating) leaves an orphan CR labeled to a BackupJob the framework is reaping. The orphan persists, and a future BackupJob with the same name (uncommon but possible after a tenant kubectl delete && kubectl apply retry) will pick up the orphan via the label-match path in findFoundationDBBackupForJob.

This isn't a "high-probability bug today" — it's a latent race that the MariaDB and CNPG drivers presumably have the same way, and one that ownerReferences would fix once the framework starts setting them. But the FoundationDBBackup intentionally does NOT carry an OwnerReference to the BackupJob (cleanup.sh explains this is by design for idempotency). So this driver is no worse than its peers — but the comment at cleanup.sh:22-26 is the only place in the tree that documents this contract, and that documentation lives in a demo script. Without it, an operator-side reader has to reverse-engineer why the operator CR survives BackupJob deletion.

Fix: add a comment to foundationdbstrategy_controller.go:735 (ensureFoundationDBBackup) explaining that the operator-side CR is intentionally NOT owner-ref'd to the BackupJob, why (idempotent ensure-by-label across BackupJob recreates with the same name), and what the cleanup contract is (the parent framework's GC, not Kubernetes ownerRefs, is responsible). Reference cleanup.sh from there so the contract is anchored in code.

Test: add TestEnsureFoundationDBBackup_DoesNotSetOwnerReference that creates a BackupJob, runs ensureFoundationDBBackup, lists the resulting FoundationDBBackup, and asserts len(b.OwnerReferences) == 0. The test pins the contract for the next person who adds an OwnerReferences: field "for safety" (which would inadvertently break the idempotent ensure-by-label semantics on BackupJob recreate).

5. requeueWithReason Status().Update has the same stale-cache class of bug that StartedAt defends against, but is unconditional

internal/backupcontroller/foundationdbstrategy_controller.go:715-726:

func (r *BackupJobReconciler) requeueWithReason(ctx context.Context, j *backupsv1alpha1.BackupJob, reason, message string) (ctrl.Result, error) {
    apimeta.SetStatusCondition(&j.Status.Conditions, metav1.Condition{
        Type:    "Ready",
        Status:  metav1.ConditionFalse,
        Reason:  reason,
        Message: message,
    })
    if err := r.Status().Update(ctx, j); err != nil {
        return ctrl.Result{}, err
    }
    return ctrl.Result{RequeueAfter: foundationdbPollInterval}, nil
}

If j carries a stale RV (which is exactly the scenario StartedAt bootstrap at foundationdbstrategy_controller.go:496-502 defends against by refetching), this Update returns a 409 Conflict. The error propagates to controller-runtime, which reschedules. On reschedule, the informer cache may STILL be stale; the loop can pin a busy-spin for the duration of the cache resync (typically seconds, sometimes longer under load).

The MariaDB and CNPG drivers have the same pattern, so this isn't FoundationDB-specific — but the FDB driver has more requeueWithReason call sites (six: FoundationDBClusterNotReady, FoundationDBClusterNotAvailable, FoundationDBClusterVersionUnknown, MissingBlobCredentialsSecret, FoundationDBBackupRunning, plus the deadline-exceeded path which markBackupJobFailed-s instead). Five of those six fire on the cold-start path where the informer cache is most likely to be stale.

Fix: in requeueWithReason, on 409 Conflict, refetch the BackupJob, re-apply the condition delta on the fresh object, and retry the Update once. Same pattern as patchStopWithRetry at internal/backupcontroller/foundationdbstrategy_controller.go:783-815. Cap the retry budget at 1 or 2 — a persistent Conflict means the parent reconciler will pick up the next change anyway.

Test: TestRequeueWithReason_RetriesOnStatusConflict — seed a BackupJob, use the controller-runtime fake-client interceptor (same pattern as TestStopOtherFoundationDBBackups_RetriesOnConflict at line 2861 of the existing test file) to return one 409 on the first Status().Update of a BackupJob, let the second through, and assert the condition lands without the reconcile returning an error.

Recommended (non-blocking, would still like to see addressed)

  • internal/backupcontroller/foundationdbstrategy_controller.go:415 says "CNPG/MariaDB use 30m for the same reason; FDB needs a touch more headroom because the first snapshot for an empty cluster still has to do range-file rotation through the backup_agent." This is plausible but unmeasured. Worth turning the 45-minute hardcode into a strategy-level knob (e.g. FoundationDBSpec.BackupTimeoutSeconds) so a tenant with a multi-TB cluster can extend it without recompiling. The restore path already has restoreTimeoutSeconds via Spec.Options; mirror it on the backup path.
  • foundationdbBackupReady (internal/backupcontroller/foundationdbstrategy_controller.go:707-721) gates on b.Status.Generations.Reconciled < b.Generation, but BackupGenerationStatus carries five other NeedsBackup* counters (internal/backupcontroller/foundationdbtypes/types.go:204-210). If the operator surfaces NeedsBackupStart long-running while Reconciled == Generation, the readiness check passes but the agent isn't actually streaming. Worth adding a guard that all NeedsBackup* counters are zero. (The operator's own semantics may make this redundant; verify with the upstream operator behavior before adding the check.)
  • internal/backupcontroller/foundationdbstrategy_controller.go:1136 (foundationdbBackupURI): the comment at 1136-1145 explains why bucket-empty falls back to "". The fallback blobstore://%s/%s URI omits secure_connection and other URLParameters that the operator-reported BackupDetails.URL would carry; the URI is "informational only" per the comment, but the inconsistency between the two return paths is worth a doc note (no behavior change needed).
  • internal/backupcontroller/foundationdbstrategy_controller.go:830: snapshot Kind constant is FDB-specific but the apiVersion borrows backupsv1alpha1.GroupVersion. That's documented as deliberate, but worth a brief note that this means apiVersion: backups.cozystack.io/v1alpha1 + kind: FoundationDBBackupSnapshot is not a registered API — it's a self-typing hint. A future reader who tries to kubectl get foundationdbbackupsnapshots will get a NotFound; the comment block should call out that this is a status-payload-only kind, not a CRD.

Closing summary of what's in scope

This is the FoundationDB twin of the recently-merged MariaDB BackupClass integration. The five blockers above are: one contract violation (snapshot apiVersion), one shipped-documentation factual error (92-scenario-user-restore.md), one demo-script tripping over its own validation (02-create-backupclass.sh placeholder), and two latent reliability issues shared with the older drivers but newly relevant because the FDB driver has more transient-error call sites (orphan CR contract comment, Status().Update conflict retry).

Reminder: "Pre-existing" is not a thing. If a problem is flagged in this review, it must be fixed in this PR — no exceptions.

Every issue listed above must have a corresponding test. No matter the severity — bug, logic error, security issue, error handling gap, documentation drift — each one must be covered by a test that fails without the fix and passes with it. Untested fixes are not fixes.

@lllamnyp Timofei Larkin (lllamnyp) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

Reviewing against target branch: main (PR #2606, "feat(platform): add foundationdb backups").

Scope

Branch contains a single commit on top of main (92930ad1a). It adds the FoundationDB backup-strategy driver: a new strategy.backups.cozystack.io/v1alpha1.FoundationDB type, controller wiring for the BackupJob and RestoreJob paths, a typed mirror of the apps.foundationdb.org/v1beta2 shape the driver writes, RBAC, an end-to-end example, and DEPRECATED markers on the chart-side backup.* values. Net diff is ~13.3k lines but ~8.7k of that is the generated CRD YAML and ~1.8k is the new test file. The hand-written code surface is roughly internal/backupcontroller/foundationdbstrategy_controller.go (1265 lines) + foundationdbtypes/types.go (281) + foundationdbapp/types.go (66) + api/backups/strategy/v1alpha1/foundationdb_types.go (157).

What I checked

  • Build (go build ./...) and vet (go vet ./...) are clean — the two pre-existing vet warnings in cmd/cozypkg and pkg/lineage are unrelated to this branch.
  • go test ./internal/backupcontroller/... passes (47 FDB-specific tests + the dispatcher tests).
  • Controller dispatcher updates in backupjob_controller.go, restorejob_controller.go, and cmd/backupstrategy-controller/main.go add the new Kind to the supported list, the dispatch switch, the cleanup no-op group, and the scheme. All four are consistent. supportedBackupStrategyKinds() and its sibling test stay in sync.
  • RBAC delta in packages/system/backupstrategy-controller/templates/rbac.yaml covers foundationdbclusters (get/list/watch), foundationdbbackups (get/list/watch/create/patch), and foundationdbrestores (get/list/watch/create). The patch verb is needed for the backupState=Stopped transition on prior CRs; no delete is granted, which matches the same "we do not own the archive" stance Altinity/MariaDB take.
  • Chart-level deprecation: api/apps/v1alpha1/foundationdb/types.go, packages/apps/foundationdb/values.yaml, values.schema.json, README.md, and packages/system/foundationdb-rd/cozyrds/foundationdb.yaml's embedded openAPISchema all carry the DEPRECATED prefix consistently. Chart templates under packages/apps/foundationdb/templates/ are untouched, so backup.enabled=true keeps rendering the original FoundationDBBackup CR unchanged (backward compat). The findConflictingInChartBackup gate catches the case where a tenant tries to run both flows against the same cluster, with a terminal Ready=False/ConflictingInChartBackup-style message — chart-rendered CR is left untouched so the user sees the diagnostic before doing the migration.
  • Snapshot schema versioning (foundationdbBackupSnapshotKind / foundationdbBackupSnapshotAPIVersion) is enforced both ways: encoder stamps both fields, decoder returns errSnapshotUnrecognised on mismatch (covered by TestDecodeFoundationDBBackupSnapshot_RejectsMismatchedAPIVersion, …_RejectsMismatchedKind, and the call-site test TestReconcileFoundationDBRestore_SnapshotAPIVersionMismatchIsTerminal). The apiVersion is tied to backupsv1alpha1.GroupVersion.String() — that's a deliberate design choice documented in the contract comment, with the trade-off (a future v1beta1 group bump would invalidate existing snapshots) called out as the maintainer's responsibility.
  • Idempotency contracts: ensureFoundationDBBackup finds by OwningJobName/Namespace labels and reuses, with TestEnsureFoundationDBBackup_IdempotentByLabel pinning it; TestEnsureFoundationDBBackup_DoesNotSetOwnerReference pins the deliberate absence of an OwnerReference (otherwise GC on BackupJob deletion would break the "delete && apply retry" reuse path). The restore side mirrors this in ensureFoundationDBRestore.
  • Conflict handling: stopOtherFoundationDBBackups retries on 409 with a refetch+re-patch loop bounded by foundationdbStopConflictRetries=5, covered by TestStopOtherFoundationDBBackups_RetriesOnConflict using an interceptor.Funcs injection. requeueWithReason does the same for Status().Update 409s, covered by TestRequeueWithReason_RetriesOnStatusConflict.
  • Deadline-expiry path: TestReconcileFoundationDB_DeadlineExpiry_StopsFoundationDBBackup pins that a 45-minute hang Stops the operator-side CR before marking the BackupJob Failed, so the backup_agent Deployment doesn't leak past the BackupJob.
  • Preconditions: empty cluster.spec.version, health.available=false, missing blob-credentials Secret, and conflicting in-chart backup all have positive+negative tests and emit distinct Ready=False/<Reason> conditions.
  • Restore options: parseFoundationDBRestoreOptions treats a malformed spec.options blob as terminal (per the in-tree convention) — TestReconcileFoundationDBRestore_MalformedOptionsIsTerminal pins this and asserts no FoundationDBRestore CR was created.
  • URI synthesis: foundationdbBackupURI prefers the operator-reported URL, falls back to bucket+backupName, returns "" when bucket is empty (refuses to graft AccountName into the bucket slot — covered by TestFoundationDBBackupURI_EmptyBucketIsEmpty).
  • Example flow (examples/backups/foundationdb/) is self-contained and consistent with the controller: label keys in cleanup.sh (backups.cozystack.io/owned-by.BackupJobName=…) match backupsv1alpha1.OwningJobNameLabel. The cluster-name mapping (foundationdbClusterNameForApp = "foundationdb-" + appName) matches both 00-helpers.sh's FDB_CLUSTER_NAME and the FoundationDB ApplicationDefinition's release.prefix. The .bucket-info.env cache (chmod 600, raw S3 creds) mirrors the established clickhouse demo pattern; not gitignored anywhere, but cleanup.sh removes it and the clickhouse demo lives under the same .gitignore rules — consistent with the existing convention.

Follow-up backlog (non-blocking — NOT a condition of LGTM)

Surfacing this explicitly so it doesn't get lost in the merge. Pick up or discard as you see fit; this does not gate the PR.

FU-1 — Add an explicit FoundationDBStrategyKind no-op branch in BackupReconciler.cleanupOnDelete

internal/backupcontroller/backup_controller.go:96-135 currently has explicit no-op branches for AltinityStrategyKind and MariaDBStrategyKind, each carrying a comment along the lines of "we do not own the archive." FoundationDB has identical semantics and additionally a more subtle twist (the operator-side FoundationDBBackup Deployment continues writing until either a new BackupJob calls stopOtherFoundationDBBackups against it, an operator picks it up manually, or examples/backups/foundationdb/cleanup.sh is run). But there is no matching case strategyv1alpha1.FoundationDBStrategyKind: branch on this PR — the kind falls through to default, which calls cleanupVeleroBackup.

  • Why it's not blocking now: cleanupVeleroBackup is a guarded no-op for any Backup that does not have veleroBackupName set in DriverMetadata. The FoundationDB driver never sets that key, so the fall-through is harmless today.

  • Why it's worth doing: this is exactly the seam that bit the MariaDB driver before the explicit branch was added — a future driver refactor that incidentally stamps velero.io/backup-name onto FDB driver metadata (e.g. during a migration, or because someone reuses a shared metadata helper) silently starts synthesising DeleteBackupRequest against Velero. The Altinity + MariaDB tests (TestBackupCleanup_Altinity_DoesNotFallThroughToVelero, TestBackupCleanup_MariaDB_DoesNotFallThroughToVelero) were written specifically to guard that boundary. FoundationDB should join the pattern for the same reason.

  • Suggested shape:

    case strategyv1alpha1.FoundationDBStrategyKind:
        // Cozystack Backup deletion does NOT delete the operator-side
        // foundationdb.org/FoundationDBBackup CR. That CR drives a continuously
        // streaming backup_agent Deployment; its lifecycle is governed by the
        // next BackupJob's stopOtherFoundationDBBackups call, by manual
        // operator action, or by examples/backups/foundationdb/cleanup.sh.
        // Same "we do not own the archive" contract as Altinity / MariaDB.
        return nil
  • Suggested test when picked up: TestBackupCleanup_FoundationDB_DoesNotFallThroughToVelero modelled exactly on the Altinity/MariaDB pairs — stamp veleroBackupNameMetadataKey + veleroBackupNamespaceMetadataKey into a FoundationDB-strategy Cozystack Backup, pre-seed a velero.io/Backup named accordingly, drive cleanupOnDelete, assert no DeleteBackupRequest was created and the Velero Backup survives. Fails without the case; passes with it.


Ticket compliance

No --ticket was supplied. The PR title "feat(platform): add foundationdb backups" matches the diff scope: a complete strategy driver, dispatch wiring, scheme registration, RBAC, deprecation markers, demo, and test coverage.

@androndo
Andrey Kolkov (androndo) merged commit 70693ff into main May 13, 2026
10 checks passed
@androndo
Andrey Kolkov (androndo) deleted the feat/foundationdb-backup branch May 13, 2026 08:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) kind/feature Categorizes issue or PR as related to a new feature size/XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants