Skip to content

Conversation

@androndo
Copy link
Contributor

@androndo androndo commented Jan 18, 2026

What this PR does

Release note

Closes #1880

Added BackupClass into BackupJob and BackupPlan for simplify UX.

The following functions related StorageRef were removed as they are no longer used after migrating to BackupClass API:
// - resolveBucketStorageRef: Previously resolved S3 credentials from Bucket storageRef
// - createS3CredsForVelero: Previously created Velero S3 credentials secrets
// - createBackupStorageLocation: Previously created Velero BackupStorageLocation resources
// - createVolumeSnapshotLocation: Previously created Velero VolumeSnapshotLocation resources

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced BackupClass API for managing backup strategies and configurations in a unified, reusable model.
  • Refactor

    • Simplified backup configuration across Jobs and Plans by consolidating storage and strategy references into a single BackupClass name reference, reducing configuration complexity.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 18, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

The changes introduce a new BackupClass custom resource that centralizes backup strategy and storage configuration. Backup jobs and plans now reference a BackupClass by name instead of individual storage and strategy references. A new resolver component matches application types to strategies within a BackupClass and derives concrete strategy references at runtime.

Changes

Cohort / File(s) Summary
API Type Definitions
api/backups/v1alpha1/backup_types.go, api/backups/v1alpha1/backupclass_types.go, api/backups/v1alpha1/backupjob_types.go, api/backups/v1alpha1/plan_types.go
Removed direct StorageRef and StrategyRef fields from BackupSpec, BackupJobSpec, and PlanSpec. Introduced new BackupClass, BackupClassSpec, BackupClassStrategy, and ApplicationSelector types to enable centralized strategy and storage configuration, with multi-strategy support and application-type matching.
Auto-generated DeepCopy
api/backups/v1alpha1/zz_generated.deepcopy.go
Updated deepcopy methods for removed StorageRef/StrategyRef fields. Added complete deepcopy implementations for new BackupClass-related types.
BackupClass Resolution
internal/backupcontroller/backupclass_resolver.go, internal/backupcontroller/backupclass_resolver_test.go
New resolver module that normalizes ApplicationRef and resolves BackupClass to derive strategy references and parameters based on application type matching. Includes comprehensive test coverage for normalization and resolution scenarios.
Backup Job Factory
internal/backupcontroller/factory/backupjob.go, internal/backupcontroller/factory/backupjob_test.go
Updated factory to normalize ApplicationRef with default API group and populate BackupClassName instead of direct storage/strategy references. Added test cases validating normalization and timestamp-based job naming.
Backup Job Controller
internal/backupcontroller/backupjob_controller.go
Refactored to resolve BackupClass and derive strategy references before routing to strategy-specific controllers. Updated logging and validation to use resolved configuration.
Job Strategy Controller
internal/backupcontroller/jobstrategy_controller.go
Extended reconcileJob method signature to accept resolved BackupClass configuration.
Plan Controller
internal/backupcontroller/plan_controller.go
Added cron parsing error handling with status condition updates. Improved requeue timing calculation.
Velero Strategy Controller
internal/backupcontroller/velerostrategy_controller.go
Refactored to use resolved BackupClass configuration for strategy and storage references. Removed legacy storage bucket resolution, S3 credentials creation, and backup storage location setup functions. Significantly simplified control flow by eliminating auxiliary storage handling.

Sequence Diagram

sequenceDiagram
    participant JobCtrl as BackupJob<br/>Controller
    participant Resolver as BackupClass<br/>Resolver
    participant BackupClass as BackupClass<br/>Resource
    participant StrategyCtrl as Strategy<br/>Controller
    
    JobCtrl->>Resolver: ResolveBackupClass(backupClassName, appRef)
    Resolver->>Resolver: NormalizeApplicationRef(appRef)
    Resolver->>BackupClass: Get(backupClassName)
    BackupClass-->>Resolver: BackupClass{strategies[...]}
    Resolver->>Resolver: Match strategy by<br/>normalized APIGroup + Kind
    Resolver-->>JobCtrl: ResolvedBackupConfig{<br/>StrategyRef, Parameters}
    JobCtrl->>StrategyCtrl: reconcile(job, resolved)
    StrategyCtrl->>StrategyCtrl: Use resolved.StrategyRef<br/>for strategy ops
    StrategyCtrl-->>JobCtrl: Result/Error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Poem

🐰 A class bundles strategies with care,
No refs scattered everywhere!
Apps match kinds with normalized grace,
BackupClass finds its rightful place.
Simpler flows, less legacy load—
A cleaner, well-structured code!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(backups): backup api rework' directly reflects the main change—a comprehensive rework of the backup API with the introduction of BackupClass as a central organizing concept replacing direct StorageRef and StrategyRef references.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @androndo, 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 significantly refactors the backup API by introducing a BackupClass abstraction. This change centralizes the definition of backup strategies and storage locations, decoupling them from individual backup requests. By referencing a BackupClass name, users can define generic backup policies that are resolved dynamically based on the application type, leading to a more streamlined, flexible, and maintainable backup system.

Highlights

  • New BackupClass API: Introduced a new BackupClass Custom Resource Definition (CRD) that centralizes backup strategy and storage configurations based on application types. This allows for more flexible and reusable backup definitions.
  • API Rework for Backup, BackupJob, and Plan: The Backup, BackupJob, and Plan resources have been updated to remove direct references to StorageRef and StrategyRef. Instead, they now reference a BackupClassName, delegating the resolution of concrete backup strategies and storage locations to the new BackupClass.
  • BackupClass Resolution Logic: A new backupclass_resolver.go component has been added to dynamically resolve the appropriate backup strategy and storage parameters from a BackupClass based on the application being backed up. This includes normalizing application API groups.
  • Controller Updates: The BackupJobReconciler has been refactored to utilize the new BackupClass resolution logic, determining the correct strategy (e.g., Velero) and passing the resolved configuration to the reconciliation functions. The PlanReconciler also now uses the BackupClassName when creating BackupJob instances.
  • Removal of Direct Storage/Strategy Management: Several helper functions previously responsible for resolving S3 credentials and creating Velero-specific resources (like BackupStorageLocation and VolumeSnapshotLocation) have been removed from velerostrategy_controller.go. This functionality is now implicitly handled through the parameters defined within the BackupClass.

🧠 New Feature in Public Preview: You can now enable Memory 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.

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-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 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 counter productive. You can react with 👍 and 👎 on @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.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

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.

Copy link
Contributor

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

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 is a significant and well-executed refactoring of the backup API. The introduction of the BackupClass CRD is a great design choice that decouples backup strategies and storage configuration from the BackupJob and Plan resources, making the system more flexible and maintainable. The changes are consistent across the API types, controllers, and new tests have been added to cover the new logic. I've identified a few areas for improvement, mainly concerning code duplication and clarity, which should be straightforward to address.

Comment on lines +334 to +337
// Verify strategy ref
if resolved.StrategyRef.Kind != "Velero" {
t.Errorf("ResolveBackupClass() StrategyRef.Kind = %v, want Velero", resolved.StrategyRef.Kind)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The assertion for the resolved strategy is a bit weak as it only checks the Kind. To make the test more robust, I suggest you also verify the Name and APIGroup of the StrategyRef. This will ensure that the correct strategy is being selected from the BackupClass for each test case.

For example, you could add checks like:

if resolved.StrategyRef.Name != "velero-strategy-vm" {
    t.Errorf("...")
}

for the "successful resolution - matches VirtualMachine strategy" test. A good way to implement this would be to add an expectedStrategyRef field to your test case struct.

Signed-off-by: Andrey Kolkov <[email protected]>
Signed-off-by: Andrey Kolkov <[email protected]>
@androndo androndo force-pushed the feat/backups-rework branch from 754ddfb to 6c11dff Compare January 19, 2026 11:05
@androndo androndo marked this pull request as ready for review January 19, 2026 11:05
@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. enhancement New feature or request labels Jan 19, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
api/backups/v1alpha1/plan_types.go (1)

31-34: Typo in comment: "Condtions" should be "Conditions".

Proposed fix
-// Condtions
+// Conditions
 const (
 	PlanConditionError = "Error"
 )
internal/backupcontroller/plan_controller.go (1)

53-62: Condition Reason should be CamelCase without spaces.

Per Kubernetes API conventions, the Reason field should be a single CamelCase word. "Failed to parse cron spec" contains spaces and should be reformatted.

Proposed fix
 		meta.SetStatusCondition(&p.Status.Conditions, metav1.Condition{
 			Type:    backupsv1alpha1.PlanConditionError,
 			Status:  metav1.ConditionTrue,
-			Reason:  "Failed to parse cron spec",
+			Reason:  "InvalidCronSpec",
 			Message: errWrapped.Error(),
 		})
internal/backupcontroller/velerostrategy_controller.go (1)

299-327: Artifact URI should use the Velero backup's namespace, not the BackupJob's namespace.

Velero backups are created in the cozy-velero namespace (see veleroNamespace constant at line 47 and the createVeleroBackup function), but the artifact URI at line 303 uses backupJob.Namespace. This causes the URI to point to a namespace that will never contain the Velero Backup. The driverMetadata already correctly captures veleroBackup.Namespace, so the URI should do the same.

Proposed fix
-	artifact := &backupsv1alpha1.BackupArtifact{
-		URI: fmt.Sprintf("velero://%s/%s", backupJob.Namespace, veleroBackup.Name),
-	}
+	artifact := &backupsv1alpha1.BackupArtifact{
+		URI: fmt.Sprintf("velero://%s/%s", veleroBackup.Namespace, veleroBackup.Name),
+	}
♻️ Duplicate comments (2)
internal/backupcontroller/factory/backupjob.go (1)

12-26: Consolidate ApplicationRef normalization to avoid drift.

This duplicates the default API group and normalization logic from the backupcontroller package. Consider moving the shared constant/function into a small common package (or API package) so both callers use a single source of truth without introducing cycles.

internal/backupcontroller/backupclass_resolver_test.go (1)

334-337: Strengthen StrategyRef assertions (Kind/Name/APIGroup).

Right now the test only checks Kind. Adding expected Name and APIGroup assertions would make the resolution tests more robust and prevent false positives.

🧹 Nitpick comments (4)
api/backups/v1alpha1/backupclass_types.go (1)

69-78: Consider adding validation for the Kind field.

The Kind field is required but has no validation to prevent empty strings. Consider adding a minimum length validation.

Proposed enhancement
 // ApplicationSelector specifies which application types a strategy applies to.
 type ApplicationSelector struct {
 	// APIGroup is the API group of the application.
 	// If not specified, defaults to "apps.cozystack.io".
 	// +optional
 	APIGroup *string `json:"apiGroup,omitempty"`

 	// Kind is the kind of the application (e.g., VirtualMachine, MySQL).
+	// +kubebuilder:validation:MinLength=1
 	Kind string `json:"kind"`
 }
internal/backupcontroller/backupclass_resolver.go (1)

54-58: Redundant APIGroup extraction after normalization.

Since applicationRef is already normalized at line 46 (which guarantees APIGroup is non-nil and non-empty), this block will always take the branch at line 57. The initial assignment and nil check are unnecessary.

Proposed simplification
-	// Determine application API group (already normalized, but extract for matching)
-	appAPIGroup := DefaultApplicationAPIGroup
-	if applicationRef.APIGroup != nil {
-		appAPIGroup = *applicationRef.APIGroup
-	}
+	// Extract normalized API group for matching
+	appAPIGroup := *applicationRef.APIGroup

 	// Find matching strategy
internal/backupcontroller/plan_controller.go (1)

66-76: Good practice: clearing error condition on success.

However, the Reason field here should also follow CamelCase convention.

Proposed fix
 		meta.SetStatusCondition(&p.Status.Conditions, metav1.Condition{
 			Type:    backupsv1alpha1.PlanConditionError,
 			Status:  metav1.ConditionFalse,
-			Reason:  "Cron spec is valid",
+			Reason:  "CronSpecValid",
 			Message: "The cron schedule has been successfully parsed",
 		})
internal/backupcontroller/factory/backupjob_test.go (1)

123-150: Strengthen the job-name assertion to prevent false positives.

Right now the test only checks length. It would be more robust to assert the exact expected name derived from the plan and scheduled timestamp.

Proposed test hardening
 import (
+	"fmt"
 	"testing"
 	"time"
@@
 			validate: func(t *testing.T, job *backupsv1alpha1.BackupJob) {
 				if job.Name == "" {
 					t.Error("BackupJob name should be generated")
 				}
-				// Name should start with plan name
-				if len(job.Name) < len("test-plan") {
-					t.Errorf("BackupJob name = %v, should start with test-plan", job.Name)
-				}
+				expected := fmt.Sprintf("test-plan-%d", tt.scheduled.Unix()/60)
+				if job.Name != expected {
+					t.Errorf("BackupJob name = %v, want %v", job.Name, expected)
+				}
 			},

return err
}

veleroBackupSpec, err := template.Template(&strategy.Spec.Template.Spec, app.Object)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
templateContext := map[string]interface{}{
"Application": app.Object,
"Parameters": resolved.Parameters,
}
veleroBackupSpec, err := template.Template(&strategy.Spec.Template.Spec, templateContext)

Also update teh docs

Copy link
Member

@lllamnyp lllamnyp left a comment

Choose a reason for hiding this comment

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

Add parameters to templating

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[backups] refactor for BackupClasses

3 participants