Skip to content

Conversation

@lllamnyp
Copy link
Member

@lllamnyp lllamnyp commented Nov 18, 2025

What this PR does

This patch creates a new backups.cozystack.io API group which includes
backup-related resources owned exclusively by Cozystack core. A
cronjob-like controller is implemented to create backup jobs that will
be handled by appropriate third-party or external controllers.

Release note

[backups] Implement the core backup API and an accompanying controller.

Summary by CodeRabbit

  • New Features
    • Adds a full backups system: create scheduled Plans (cron), run BackupJobs with lifecycle phases (Pending→Running→Succeeded/Failed), and produce Backup artifacts with metadata.
    • Adds RestoreJobs to restore from stored Backups and track restore progress/status.
    • Exposes new API resources (Plan, BackupJob, Backup, RestoreJob) so backups and restores can be declared and observed via the platform.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 18, 2025

Walkthrough

Adds a new backups.cozystack.io v1alpha1 API group (Plan, BackupJob, Backup, RestoreJob), CRD manifests and scheme wiring, autogenerated deepcopy code, a PlanReconciler that schedules BackupJobs from Plan cron schedules, a BackupJob factory, design documentation, codegen updates, and dependency additions.

Changes

Cohort / File(s) Summary
API Type Definitions
api/backups/v1alpha1/plan_types.go, api/backups/v1alpha1/backupjob_types.go, api/backups/v1alpha1/backup_types.go, api/backups/v1alpha1/restorejob_types.go, api/backups/v1alpha1/groupversion_info.go
Add v1alpha1 API types and group/version wiring for Plan, BackupJob, Backup, RestoreJob: specs, statuses, phase enums, kubebuilder markers, SchemeBuilder and AddToScheme registrations.
Generated DeepCopy Methods
api/backups/v1alpha1/zz_generated.deepcopy.go
Add autogenerated DeepCopyInto/DeepCopy/DeepCopyObject implementations for all new v1alpha1 types.
CRD Manifests
packages/system/backup-controller/definitions/backups.cozystack.io_*.yaml
Add OpenAPI v3 CRD YAMLs for Plan, BackupJob, Backup, RestoreJob with schemas, required fields, validation and condition structures.
Controller Logic
internal/backupcontroller/plan_controller.go
Add PlanReconciler: fetch Plans, parse cron schedule, compute next run, create BackupJob when due, set owner reference, handle requeue and already-exists.
Factory Helper
internal/backupcontroller/factory/backupjob.go
Add BackupJob factory to construct BackupJob objects from a Plan and scheduled time (name generation, namespace, copied refs).
Design Documentation
api/backups/v1alpha1/DESIGN.md
Add comprehensive design doc describing core API resources, lifecycle, driver contracts, and multi-driver architecture.
Codegen / Tooling
hack/update-codegen.sh, api/.gitattributes, packages/system/backup-controller/definitions/.gitattributes
Update codegen script to use TMPDIR and move CRD artifacts; mark generated deepcopy/CRD files as generated in .gitattributes.
Dependencies
go.mod
Add direct dependencies: github.com/go-logr/logr, github.com/go-logr/zapr, github.com/prometheus/client_golang, github.com/robfig/cron/v3, go.uber.org/zap.

Sequence Diagram(s)

sequenceDiagram
    participant User as User/Client
    participant K8sAPI as Kubernetes API
    participant Plan as Plan CRD
    participant Reconciler as PlanReconciler
    participant Cron as Cron Parser
    participant Factory as BackupJob Factory
    participant BackupJob as BackupJob CRD
    participant Strategy as Strategy Driver

    User->>K8sAPI: Create Plan (schedule, refs)
    K8sAPI-->>Reconciler: Watch event / Reconcile request
    Reconciler->>K8sAPI: Get Plan
    Reconciler->>Cron: Parse Plan.spec.schedule.cron & compute next run
    Cron-->>Reconciler: Next run time (tNext)
    alt tNext <= now
        Reconciler->>Factory: Build BackupJob(Plan, tNext)
        Factory-->>Reconciler: BackupJob object
        Reconciler->>K8sAPI: Create BackupJob (ownerRef=Plan)
        K8sAPI-->>BackupJob: Persisted
        Note over BackupJob,Strategy: Strategy driver observes BackupJob and executes backup
    else not due
        Reconciler->>K8sAPI: Requeue after computed duration
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • plan_controller.go — cron parsing, next-run calculation, requeue/backoff behavior, ownerRef and create error handling.
  • CRD manifests — verify OpenAPI schema alignment with Go types and required/atomic map semantics.
  • Factory helper — name-generation format and deep-copy correctness for references.
  • zz_generated.deepcopy.go & .gitattributes — ensure generated code/classification is consistent.
  • go.mod — confirm dependency versions and usage rationale.

Suggested reviewers

  • kvaps

Poem

🐰 I nibbled code beneath moonlight's glow,
Plans set their crons and scheduled jobs in tow.
Backup bundles wrapped with careful art,
Drivers wake and play their part —
Hop, safe artifacts, into the burrow you go! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title '[backups] Implement core backup Plan controller' clearly and concisely summarizes the primary change: implementing a controller for the backup Plan resource in the core backups API.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/backup-controller

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.

@lllamnyp lllamnyp force-pushed the feat/backup-controller branch 3 times, most recently from 3a2c842 to 9fcb2cc Compare November 19, 2025 20:27
@lllamnyp lllamnyp force-pushed the feat/backup-controller branch 3 times, most recently from c70a53a to cb24e7f Compare December 4, 2025 15:37
@lllamnyp lllamnyp marked this pull request as ready for review December 4, 2025 16:04
@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. enhancement New feature or request labels Dec 4, 2025
@lllamnyp lllamnyp force-pushed the feat/backup-controller branch from cb24e7f to 47c4655 Compare December 4, 2025 16:11
@lllamnyp lllamnyp changed the title [backups] WIP [backups] Implement core backup Plan controller Dec 4, 2025
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: 6

🧹 Nitpick comments (6)
internal/backupcontroller/factory/backupjob.go (1)

12-28: Consider adding TriggeredBy and ownerReferences.

The DESIGN.md contract (lines 151-152) specifies that BackupJobs created by Plans should have spec.triggeredBy = "Plan" and ownerReferences set. Currently, this factory doesn't set either field.

Consider applying this diff:

 func BackupJob(p *backupsv1alpha1.Plan, scheduledFor time.Time) *backupsv1alpha1.BackupJob {
 	job := &backupsv1alpha1.BackupJob{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      fmt.Sprintf("%s-%d", p.Name, scheduledFor.Unix()/60),
 			Namespace: p.Namespace,
+			OwnerReferences: []metav1.OwnerReference{
+				*metav1.NewControllerRef(p, backupsv1alpha1.GroupVersion.WithKind("Plan")),
+			},
 		},
 		Spec: backupsv1alpha1.BackupJobSpec{
 			PlanRef: &corev1.LocalObjectReference{
 				Name: p.Name,
 			},
 			ApplicationRef: *p.Spec.ApplicationRef.DeepCopy(),
 			StorageRef:     *p.Spec.StorageRef.DeepCopy(),
 			StrategyRef:    *p.Spec.StrategyRef.DeepCopy(),
+			TriggeredBy:    "Plan",
 		},
 	}
 	return job
 }
packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml (1)

68-82: Consider making schedule.cron a required field.

The description at line 75 states "omitting this field is not allowed" since only cron type is currently supported. However, the cron field is not in the required list for the schedule object. This creates a mismatch between documentation and validation.

Consider applying this diff to make the validation explicit:

               schedule:
                 description: Schedule specifies when backup copies are created.
                 properties:
                   cron:
                     description: |-
                       Cron contains the cron spec for scheduling backups. Must be
                       specified if the schedule type is `cron`. Since only `cron` is
                       supported, omitting this field is not allowed.
                     type: string
                   type:
                     description: |-
                       Type is the type of schedule specification. Supported values are
                       [`cron`]. If omitted, defaults to `cron`.
                     type: string
+                required:
+                - cron
                 type: object

Alternatively, implement validation via a CEL rule or validating webhook if you want to support future schedule types where cron is optional.

packages/system/backup-controller/definitions/backups.cozystack.io_backups.yaml (1)

1-234: Consider adding status subresource configuration.

The CRD definition is missing the subresources field to enable the status subresource. This is a recommended practice for Kubernetes CRDs with a status field, as it allows separate RBAC control and optimistic concurrency for status updates.

Add the following after line 16 (under versions:):

   - name: v1alpha1
+    subresources:
+      status: {}
     schema:
internal/backupcontroller/plan_controller.go (1)

29-61: Add deletion handling and optimize requeue strategy.

The reconciler has two issues:

  1. Missing deletion handling: The reconciler doesn't check if the Plan is being deleted (has a deletionTimestamp), which could lead to creating BackupJobs for Plans that are being removed.

  2. Suboptimal requeue strategy: The reconciler always requeues after startingDeadlineSeconds (300s) on success, even when the next scheduled run might be much further in the future. This causes unnecessary reconciliations.

Apply these improvements:

 func (r *PlanReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
 	log := log.FromContext(ctx)
 
 	log.V(2).Info("reconciling")
 
 	p := &backupsv1alpha1.Plan{}
 
 	if err := r.Get(ctx, client.ObjectKey{Namespace: req.Namespace, Name: req.Name}, p); err != nil {
 		if apierrors.IsNotFound(err) {
 			log.V(3).Info("Plan not found")
 			return ctrl.Result{}, nil
 		}
 		return ctrl.Result{}, err
 	}
+
+	// Skip reconciliation if Plan is being deleted
+	if !p.DeletionTimestamp.IsZero() {
+		return ctrl.Result{}, nil
+	}
 
 	tCheck := time.Now().Add(-startingDeadlineSeconds)
 	tNext := nextCronAfter(p.Spec.Schedule.Cron, tCheck)
 
 	if time.Now().Before(tNext) {
 		return ctrl.Result{RequeueAfter: tNext.Sub(time.Now())}, nil
 	}
 
 	run := factory.BackupJob(p, tNext)
 
 	if err := r.Create(ctx, run); err != nil {
 		if apierrors.IsAlreadyExists(err) {
-			return ctrl.Result{RequeueAfter: startingDeadlineSeconds}, nil
+			// Job already exists, compute next run
+			tNextRun := nextCronAfter(p.Spec.Schedule.Cron, time.Now())
+			delay := tNextRun.Sub(time.Now())
+			if delay < minRequeueDelay {
+				delay = minRequeueDelay
+			}
+			return ctrl.Result{RequeueAfter: delay}, nil
 		}
 		return ctrl.Result{}, err
 	}
 
-	return ctrl.Result{RequeueAfter: startingDeadlineSeconds}, nil
+	// Compute next run after successful creation
+	tNextRun := nextCronAfter(p.Spec.Schedule.Cron, time.Now())
+	delay := tNextRun.Sub(time.Now())
+	if delay < minRequeueDelay {
+		delay = minRequeueDelay
+	}
+	return ctrl.Result{RequeueAfter: delay}, nil
 }
packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml (1)

1-231: Consider adding status subresource configuration.

Similar to the Backup CRD, this CRD is missing the subresources field to enable the status subresource. This is a recommended practice for Kubernetes CRDs with a status field.

Add the following after line 16 (under versions:):

   - name: v1alpha1
+    subresources:
+      status: {}
     schema:
packages/system/backup-controller/definitions/backups.cozystack.io_restorejobs.yaml (1)

1-168: Consider adding status subresource configuration.

Similar to the other CRDs in this PR, this CRD is missing the subresources field to enable the status subresource.

Add the following after line 16 (under versions:):

   - name: v1alpha1
+    subresources:
+      status: {}
     schema:
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52ebcae and 47c4655.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (17)
  • api/.gitattributes (1 hunks)
  • api/backups/v1alpha1/DESIGN.md (1 hunks)
  • api/backups/v1alpha1/backup_types.go (1 hunks)
  • api/backups/v1alpha1/backupjob_types.go (1 hunks)
  • api/backups/v1alpha1/groupversion_info.go (1 hunks)
  • api/backups/v1alpha1/plan_types.go (1 hunks)
  • api/backups/v1alpha1/restorejob_types.go (1 hunks)
  • api/backups/v1alpha1/zz_generated.deepcopy.go (1 hunks)
  • go.mod (1 hunks)
  • hack/update-codegen.sh (2 hunks)
  • internal/backupcontroller/factory/backupjob.go (1 hunks)
  • internal/backupcontroller/plan_controller.go (1 hunks)
  • packages/system/backup-controller/definitions/.gitattributes (1 hunks)
  • packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml (1 hunks)
  • packages/system/backup-controller/definitions/backups.cozystack.io_backups.yaml (1 hunks)
  • packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml (1 hunks)
  • packages/system/backup-controller/definitions/backups.cozystack.io_restorejobs.yaml (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Use Controller-runtime patterns and kubebuilder style for Go code

Files:

  • api/backups/v1alpha1/plan_types.go
  • api/backups/v1alpha1/groupversion_info.go
  • internal/backupcontroller/plan_controller.go
  • api/backups/v1alpha1/backup_types.go
  • internal/backupcontroller/factory/backupjob.go
  • api/backups/v1alpha1/restorejob_types.go
  • api/backups/v1alpha1/backupjob_types.go
  • api/backups/v1alpha1/zz_generated.deepcopy.go
{go.mod,go.sum}

📄 CodeRabbit inference engine (AGENTS.md)

Do not manually modify go.mod and go.sum files; use go get command instead

Files:

  • go.mod
**/zz_generated.*.go

📄 CodeRabbit inference engine (AGENTS.md)

Do not edit auto-generated Go files matching pattern zz_generated.*.go

Files:

  • api/backups/v1alpha1/zz_generated.deepcopy.go
🧠 Learnings (3)
📚 Learning: 2025-11-27T11:21:45.808Z
Learnt from: CR
Repo: cozystack/cozystack PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T11:21:45.808Z
Learning: Applies to **/zz_generated.*.go : Do not edit auto-generated Go files matching pattern `zz_generated.*.go`

Applied to files:

  • api/.gitattributes
📚 Learning: 2025-11-27T11:21:45.808Z
Learnt from: CR
Repo: cozystack/cozystack PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T11:21:45.808Z
Learning: Applies to **/*.go : Use Controller-runtime patterns and kubebuilder style for Go code

Applied to files:

  • hack/update-codegen.sh
📚 Learning: 2025-11-27T11:21:45.808Z
Learnt from: CR
Repo: cozystack/cozystack PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T11:21:45.808Z
Learning: Applies to {go.mod,go.sum} : Do not manually modify `go.mod` and `go.sum` files; use `go get` command instead

Applied to files:

  • go.mod
🧬 Code graph analysis (5)
api/backups/v1alpha1/groupversion_info.go (3)
pkg/apiserver/apiserver.go (1)
  • Scheme (53-53)
pkg/apis/apps/v1alpha1/register.go (2)
  • addKnownTypes (56-59)
  • RegisterDynamicTypes (67-84)
api/v1alpha1/cozystackresourcedefinitions_types.go (1)
  • init (43-45)
internal/backupcontroller/plan_controller.go (2)
api/backups/v1alpha1/plan_types.go (1)
  • Plan (24-29)
internal/backupcontroller/factory/backupjob.go (1)
  • BackupJob (12-28)
internal/backupcontroller/factory/backupjob.go (2)
api/backups/v1alpha1/backupjob_types.go (2)
  • BackupJob (78-84)
  • BackupJobSpec (25-42)
api/backups/v1alpha1/plan_types.go (1)
  • Plan (24-29)
packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml (1)
api/v1alpha1/cozystackresourcedefinitions_types.go (1)
  • Application (47-62)
api/backups/v1alpha1/backupjob_types.go (1)
internal/backupcontroller/factory/backupjob.go (1)
  • BackupJob (12-28)
🪛 LanguageTool
api/backups/v1alpha1/DESIGN.md

[grammar] ~220-~220: Ensure spelling is correct
Context: ..., Running, Succeeded, Failed`. BackupJob contract with drivers * Core **creat...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[locale-violation] ~222-~222: In American English, ‘afterward’ is the preferred variant. ‘Afterwards’ is more commonly used in British English and other dialects.
Context: ...Joband must treatspec` as immutable afterwards. * Each driver controller: * Watches...

(AFTERWARDS_US)


[style] ~242-~242: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... * Set status.completedAt. * Set status.phase = Succeeded. 5. On fai...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~247-~247: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... * Set status.phase = Failed. * Set status.message and conditions. Drive...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~372-~372: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...* Set status.phase = Failed. * Set status.message and conditions. Drive...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🪛 markdownlint-cli2 (0.18.1)
api/backups/v1alpha1/DESIGN.md

98-98: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


138-138: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


164-164: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


168-168: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


184-184: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


205-205: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


220-220: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


261-261: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


274-274: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


286-286: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


319-319: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


332-332: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


344-344: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🔇 Additional comments (17)
api/.gitattributes (1)

1-1: LGTM!

Correctly marks generated deepcopy files for linguist, aligning with the generated artifacts added in this PR.

packages/system/backup-controller/definitions/.gitattributes (1)

1-1: LGTM!

Appropriately marks all CRD definitions in this directory as generated, since they're produced by controller-gen.

hack/update-codegen.sh (2)

26-30: LGTM!

Good practice using a temporary directory with cleanup trap. The three-way CRD split (cozystack-controller, resource-definition, backup-controller) properly organizes generated artifacts by controller ownership.


61-68: LGTM!

The relocation logic correctly routes generated CRDs to their respective destinations. The glob pattern backups.cozystack.io*.yaml at line 66 ensures all backup-related CRDs are moved to the backup controller definitions directory.

api/backups/v1alpha1/backup_types.go (6)

1-11: LGTM!

Package declaration and imports follow kubebuilder conventions correctly.


13-21: LGTM!

BackupPhase constants follow Kubernetes API conventions with appropriate states for the backup lifecycle.


23-37: LGTM!

BackupArtifact struct is well-designed with appropriate optional fields and clear documentation for storage URIs.


39-66: LGTM!

BackupSpec properly captures all necessary references (ApplicationRef, PlanRef, StorageRef, StrategyRef) and metadata for backup artifacts, aligning with the design in DESIGN.md.


68-93: LGTM!

BackupStatus and Backup resource definitions follow controller-runtime patterns correctly with proper kubebuilder markers for CRD generation.


95-102: LGTM!

BackupList follows standard Kubernetes list conventions.

go.mod (1)

9-18: LGTM!

Moving these dependencies from indirect to direct correctly reflects their usage in the new backup controller (cron scheduling, structured logging, metrics).

packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml (4)

1-15: LGTM!

CRD metadata and naming conventions follow Kubernetes standards correctly.


46-67: LGTM!

The applicationRef field properly uses TypedLocalObjectReference with atomic map typing, following Kubernetes API conventions.


83-104: LGTM!

The storageRef field properly uses TypedLocalObjectReference with correct validation and atomic map typing.


105-132: LGTM!

The strategyRef field and required fields list are properly defined, ensuring all necessary Plan spec fields are validated.

api/backups/v1alpha1/groupversion_info.go (1)

39-45: LGTM! Ensure types are registered in their respective files.

The addKnownTypes function correctly sets up the group version registration. The empty type list in scheme.AddKnownTypes is expected, as types should be registered via SchemeBuilder.Register() calls in init() functions within their respective type definition files (e.g., plan_types.go, backupjob_types.go, etc.).

Note: I've flagged the missing init() function in plan_types.go separately. Ensure all type files (Plan, BackupJob, RestoreJob, Backup) include similar registration.

api/backups/v1alpha1/zz_generated.deepcopy.go (1)

1-478: Autogenerated deepcopy code looks correct; rely on controller-gen for changes

This file is clearly marked as controller-gen output and the deepcopy implementations for all types (including slices, maps, pointers, and conditions) follow the standard Kubernetes patterns with appropriate nil checks and deep copies. No issues spotted. Any future adjustments should be made by updating the type definitions and re-running controller-gen rather than hand-editing this file, in line with the zz_generated.*.go guideline.

Comment on lines +74 to +93
// +kubebuilder:object:root=true

// BackupJob represents a single execution of a backup.
// It is typically created by a Plan controller when a schedule fires.
type BackupJob struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec BackupJobSpec `json:"spec,omitempty"`
Status BackupJobStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// BackupJobList contains a list of BackupJobs.
type BackupJobList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []BackupJob `json:"items"`
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Register types with SchemeBuilder.

The BackupJob and BackupJobList types are not registered with the SchemeBuilder. Add an init() function to register these types.

Add the following after the type definitions:

func init() {
	SchemeBuilder.Register(&BackupJob{}, &BackupJobList{})
}

This ensures the types are properly wired into the runtime scheme. As per coding guidelines, controller-runtime patterns require explicit type registration.

🤖 Prompt for AI Agents
In api/backups/v1alpha1/backupjob_types.go around lines 74-93, the BackupJob and
BackupJobList types are not registered with the package SchemeBuilder; add an
init() function after the type definitions that calls SchemeBuilder.Register
with &BackupJob{} and &BackupJobList{} so the types are wired into the runtime
scheme used by controller-runtime.

Comment on lines 20 to 78
// +kubebuilder:object:root=true

// Plan describes the schedule, method and storage location for the
// backup of a given target application.
type Plan struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec PlanSpec `json:"spec,omitempty"`
}

// +kubebuilder:object:root=true

// PlanList contains a list of backup Plans.
type PlanList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Plan `json:"items"`
}

// PlanSpec references the storage, the strategy, the application to be
// backed up and specifies the timetable on which the backups will run.
type PlanSpec struct {
// ApplicationRef holds a reference to the managed application,
// whose state and configuration must be backed up.
ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"`

// StorageRef holds a reference to the Storage object that
// describes the location where the backup will be stored.
StorageRef corev1.TypedLocalObjectReference `json:"storageRef"`

// StrategyRef holds a reference to the Strategy object that
// describes, how a backup copy is to be created.
StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"`

// Schedule specifies when backup copies are created.
Schedule PlanSchedule `json:"schedule"`
}

// PlanSchedule specifies when backup copies are created.
type PlanSchedule struct {
// Type is the type of schedule specification. Supported values are
// [`cron`]. If omitted, defaults to `cron`.
// +optional
Type PlanScheduleType `json:"type,omitempty"`

// Cron contains the cron spec for scheduling backups. Must be
// specified if the schedule type is `cron`. Since only `cron` is
// supported, omitting this field is not allowed.
// +optional
Cron string `json:"cron,omitempty"`
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Register types with SchemeBuilder.

The Plan and PlanList types are not registered with the SchemeBuilder. Based on patterns in the codebase, you should add an init() function to register these types.

Add the following after the type definitions:

func init() {
	SchemeBuilder.Register(&Plan{}, &PlanList{})
}

This ensures the types are properly wired into the runtime scheme. As per coding guidelines, controller-runtime patterns require explicit type registration.

🤖 Prompt for AI Agents
In api/backups/v1alpha1/plan_types.go around lines 20 to 71, the Plan and
PlanList types are declared but not registered with the package SchemeBuilder;
add an init() function after the type definitions that calls
SchemeBuilder.Register(&Plan{}, &PlanList{}) to register them with the runtime
scheme so controller-runtime can recognize these CRD types at runtime.

Comment on lines +66 to +77
// Cron contains the cron spec for scheduling backups. Must be
// specified if the schedule type is `cron`. Since only `cron` is
// supported, omitting this field is not allowed.
// +optional
Cron string `json:"cron,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clarify the Cron field requirement.

The comment states "omitting this field is not allowed," but the field is marked as +optional. This creates a contradiction. If the Cron field is truly required when Type is "cron", consider either:

  1. Removing the +optional marker and making it required at the schema level, or
  2. Updating the comment to clarify that validation happens at runtime/webhook level
🤖 Prompt for AI Agents
In api/backups/v1alpha1/plan_types.go around lines 66 to 70, the field comment
for Cron contradicts the +optional marker by saying "omitting this field is not
allowed"; update to remove the contradiction by either removing the +optional
tag and making the field required in the API schema (adjust kubebuilder markers
accordingly) or by clarifying the comment to state that Cron is optional in the
schema but required at runtime when Type == "cron" and that validation is
enforced via webhook/validation logic; pick one approach and keep the code and
comment consistent.

Comment on lines +62 to +80
// +kubebuilder:object:root=true

// RestoreJob represents a single execution of a restore from a Backup.
type RestoreJob struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec RestoreJobSpec `json:"spec,omitempty"`
Status RestoreJobStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// RestoreJobList contains a list of RestoreJobs.
type RestoreJobList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []RestoreJob `json:"items"`
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Register types with SchemeBuilder.

The RestoreJob and RestoreJobList types are not registered with the SchemeBuilder. Add an init() function to register these types.

Add the following after the type definitions:

func init() {
	SchemeBuilder.Register(&RestoreJob{}, &RestoreJobList{})
}

This ensures the types are properly wired into the runtime scheme. As per coding guidelines, controller-runtime patterns require explicit type registration.

🤖 Prompt for AI Agents
In api/backups/v1alpha1/restorejob_types.go around lines 62 to 80, the
RestoreJob and RestoreJobList types are not registered with the SchemeBuilder;
add an init() function immediately after the type definitions that calls
SchemeBuilder.Register with pointers to RestoreJob and RestoreJobList (i.e.,
register &RestoreJob{} and &RestoreJobList{}) so the types are wired into the
controller-runtime runtime scheme.

Comment on lines 70 to 76
func nextCronAfter(c string, t time.Time) time.Time {
schedule, err := cron.ParseStandard(c)
if err != nil {
return time.Time{}
}
return schedule.Next(t)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle cron parse errors explicitly.

The nextCronAfter function returns a zero time on parse error, which could lead to attempting to create a BackupJob with an invalid scheduled time. This would result in nonsensical BackupJob names and timestamps.

Apply this diff to handle parse errors properly:

-func nextCronAfter(c string, t time.Time) time.Time {
+func nextCronAfter(c string, t time.Time) (time.Time, error) {
 	schedule, err := cron.ParseStandard(c)
 	if err != nil {
-		return time.Time{}
+		return time.Time{}, err
 	}
-	return schedule.Next(t)
+	return schedule.Next(t), nil
 }

Then update the caller in Reconcile to handle the error:

-	tNext := nextCronAfter(p.Spec.Schedule.Cron, tCheck)
+	tNext, err := nextCronAfter(p.Spec.Schedule.Cron, tCheck)
+	if err != nil {
+		log.Error(err, "failed to parse cron schedule")
+		// Requeue with a longer delay for invalid schedules
+		return ctrl.Result{RequeueAfter: 5 * time.Minute}, nil
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func nextCronAfter(c string, t time.Time) time.Time {
schedule, err := cron.ParseStandard(c)
if err != nil {
return time.Time{}
}
return schedule.Next(t)
}
func nextCronAfter(c string, t time.Time) (time.Time, error) {
schedule, err := cron.ParseStandard(c)
if err != nil {
return time.Time{}, err
}
return schedule.Next(t), nil
}
🤖 Prompt for AI Agents
In internal/backupcontroller/plan_controller.go around lines 70-76,
nextCronAfter currently swallows cron.ParseStandard errors by returning a zero
time which can produce invalid BackupJob names/timestamps; change nextCronAfter
to return (time.Time, error) so it returns the parsed next time on success and
the parse error on failure, and update the Reconcile caller to check the error
before creating a BackupJob (skip or requeue the reconcile when the cron
expression is invalid, and log the parse error with context).

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: 4

🧹 Nitpick comments (8)
api/backups/v1alpha1/restorejob_types.go (1)

13-80: RestoreJob API shape looks good; consider status subresource and phase validation

The RestoreJob spec/status types follow standard Kubernetes patterns and match how similar resources are modeled elsewhere in the repo. Two non-blocking improvements you might consider:

  • Add a status subresource so controllers can safely update .status without racing on .spec:

    // +kubebuilder:object:root=true
    // +kubebuilder:subresource:status
    type RestoreJob struct {
        ...
    }
  • Tighten the CRD schema for Phase by adding an enum, to align with the defined constants:

    // +kubebuilder:validation:Enum=Pending;Running;Succeeded;Failed
    Phase RestoreJobPhase `json:"phase,omitempty"`

Both can be added incrementally without changing the Go API surface.

internal/backupcontroller/factory/backupjob.go (1)

12-27: BackupJob factory logic is sound; consider clarifying the name format

The factory correctly:

  • Derives namespace/name from the Plan and scheduled time.
  • Sets PlanRef and copies ApplicationRef/StorageRef/StrategyRef from the Plan spec, avoiding accidental aliasing via DeepCopy().

One small optional improvement: the scheduledFor.Unix()/60 naming scheme is a bit opaque to readers. A brief comment explaining that the suffix is “minutes since epoch” (and why minute-level granularity is chosen) or switching to a more self-describing format (e.g. YYYYMMDD-HHMM in UTC) would make debugging and manual inspection of BackupJobs easier.

hack/update-codegen.sh (1)

26-30: Codegen CRD moves are fragile with set -e and unguarded globs

The new TMPDIR workflow and split of CRDs into dedicated directories is a nice improvement, but with set -o errexit the unguarded mv calls on globs can cause the script to fail if a pattern has no matches (e.g., if backups CRDs are temporarily removed or renamed, or if no “other” CRDs are emitted):

  • mv ${TMPDIR}/backups.cozystack.io*.yaml ...
  • mv ${TMPDIR}/*.yaml ...

In such cases, Bash leaves the glob unexpanded and mv exits non‑zero.

To make this more robust, you can enable nullglob and guard the moves, and quote variables:

 CONTROLLER_GEN="go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.16.4"
-TMPDIR=$(mktemp -d)
-COZY_CONTROLLER_CRDDIR=packages/system/cozystack-controller/crds
-COZY_RD_CRDDIR=packages/system/cozystack-resource-definition-crd/definition
-BACKUPS_CORE_CRDDIR=packages/system/backup-controller/definitions
-trap 'rm -rf ${TMPDIR}' EXIT
+TMPDIR="$(mktemp -d)"
+COZY_CONTROLLER_CRDDIR="packages/system/cozystack-controller/crds"
+COZY_RD_CRDDIR="packages/system/cozystack-resource-definition-crd/definition"
+BACKUPS_CORE_CRDDIR="packages/system/backup-controller/definitions"
+trap 'rm -rf "${TMPDIR}"' EXIT
+shopt -s nullglob

@@
-$CONTROLLER_GEN rbac:roleName=manager-role crd paths="./api/..." output:crd:artifacts:config=${TMPDIR} 
-
-mv ${TMPDIR}/cozystack.io_cozystackresourcedefinitions.yaml \
-        ${COZY_RD_CRDDIR}/cozystack.io_cozystackresourcedefinitions.yaml
-
-mv ${TMPDIR}/backups.cozystack.io*.yaml ${BACKUPS_CORE_CRDDIR}/
-
-mv ${TMPDIR}/*.yaml ${COZY_CONTROLLER_CRDDIR}/
+$CONTROLLER_GEN rbac:roleName=manager-role crd paths="./api/..." output:crd:artifacts:config="${TMPDIR}"
+
+mv "${TMPDIR}/cozystack.io_cozystackresourcedefinitions.yaml" \
+   "${COZY_RD_CRDDIR}/cozystack.io_cozystackresourcedefinitions.yaml"
+
+backups_crds=("${TMPDIR}"/backups.cozystack.io*.yaml)
+if ((${#backups_crds[@]})); then
+  mv "${backups_crds[@]}" "${BACKUPS_CORE_CRDDIR}/"
+fi
+
+other_crds=("${TMPDIR}"/*.yaml)
+if ((${#other_crds[@]})); then
+  mv "${other_crds[@]}" "${COZY_CONTROLLER_CRDDIR}/"
+fi

This keeps the intent while avoiding spurious failures if CRD outputs change over time.

Also applies to: 61-68

api/backups/v1alpha1/plan_types.go (1)

13-71: Plan/PlanSchedule types are well-shaped; consider tightening validation to match docs

The Plan, PlanSpec, and PlanSchedule types look good and fit kubebuilder/controller-runtime conventions. The comments, however, promise semantics that aren’t currently enforced at the schema level:

  • PlanScheduleType:
    • Comment says supported values are cron and that omitting Type defaults it to cron, but there is no default or Enum marker.
  • PlanSchedule.Cron:
    • Comment says “omitting this field is not allowed”, but the field is marked +optional and omitempty.

To align CRD behavior with the documented contract, you could do something like:

type PlanScheduleType string

const (
    PlanScheduleTypeEmpty PlanScheduleType = ""
    PlanScheduleTypeCron  PlanScheduleType = "cron"
)

// PlanSchedule specifies when backup copies are created.
type PlanSchedule struct {
    // Type is the type of schedule specification. Supported values are [`cron`].
    // If omitted, defaults to `cron`.
    // +kubebuilder:validation:Enum=cron
    // +kubebuilder:default=cron
    Type PlanScheduleType `json:"type,omitempty"`

    // Cron contains the cron spec for scheduling backups.
    // Must be specified if the schedule type is `cron`.
    // +kubebuilder:validation:MinLength=1
    Cron string `json:"cron"`
}

This keeps the Go API the same while ensuring invalid Plans are rejected early by the API server.

As per coding guidelines, tightening kubebuilder validation here will make the CRDs more robust and self-documenting.

internal/backupcontroller/plan_controller.go (1)

18-21: Remove or use minRequeueDelay

minRequeueDelay is declared but never used; only startingDeadlineSeconds participates in requeue decisions. Either wire minRequeueDelay into the scheduling logic or drop the constant to avoid dead code.

api/backups/v1alpha1/backup_types.go (1)

68-83: Consider adding +kubebuilder:subresource:status to Backup

BackupStatus is clearly controller-owned (phase, artifact, conditions), but Backup currently only has +kubebuilder:object:root=true. Adding a status subresource:

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
type Backup struct {
    ...
}

would follow standard controller-runtime/kubebuilder patterns and allow controllers to update status via /status without risking concurrent spec updates.

Also applies to: 84-93

api/backups/v1alpha1/backupjob_types.go (1)

24-42: Align BackupJob API with DESIGN.md (TriggeredBy) and consider status subresource

Two small inconsistencies worth addressing:

  1. DESIGN.md documents a BackupJobSpec field:
// Informational: what triggered this run ("Plan", "Manual", etc.).
TriggeredBy string `json:"triggeredBy,omitempty"`

but the actual BackupJobSpec (and the CRD) do not include TriggeredBy. Either:

  • add TriggeredBy to BackupJobSpec and regenerate the CRD, or
  • update DESIGN.md to drop that field if you intentionally decided against it.
  1. As with Backup, BackupJob has a controller-updated Status but no +kubebuilder:subresource:status. Adding it:
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
type BackupJob struct {
    ...
}

would better match standard controller-runtime/kubebuilder practice and avoid spec/status write races.

Also applies to: 44-84

api/backups/v1alpha1/DESIGN.md (1)

88-176: Address markdownlint MD036 (“emphasis used instead of a heading”) if CI enforces it

Sections like **Purpose**, **Key fields (spec)**, and **Backup contract with drivers** are styled as bold text rather than proper headings, which triggers markdownlint’s MD036 rule.

If markdownlint is part of your checks, consider:

  • Converting these to real headings, e.g. #### Purpose, #### Key fields (spec), etc., or
  • Disabling MD036 for this file (or specific lines) if you prefer the current style.

Also applies to: 253-308, 378-407

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52ebcae and 47c4655.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (17)
  • api/.gitattributes (1 hunks)
  • api/backups/v1alpha1/DESIGN.md (1 hunks)
  • api/backups/v1alpha1/backup_types.go (1 hunks)
  • api/backups/v1alpha1/backupjob_types.go (1 hunks)
  • api/backups/v1alpha1/groupversion_info.go (1 hunks)
  • api/backups/v1alpha1/plan_types.go (1 hunks)
  • api/backups/v1alpha1/restorejob_types.go (1 hunks)
  • api/backups/v1alpha1/zz_generated.deepcopy.go (1 hunks)
  • go.mod (1 hunks)
  • hack/update-codegen.sh (2 hunks)
  • internal/backupcontroller/factory/backupjob.go (1 hunks)
  • internal/backupcontroller/plan_controller.go (1 hunks)
  • packages/system/backup-controller/definitions/.gitattributes (1 hunks)
  • packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml (1 hunks)
  • packages/system/backup-controller/definitions/backups.cozystack.io_backups.yaml (1 hunks)
  • packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml (1 hunks)
  • packages/system/backup-controller/definitions/backups.cozystack.io_restorejobs.yaml (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Use Controller-runtime patterns and kubebuilder style for Go code

Files:

  • internal/backupcontroller/factory/backupjob.go
  • internal/backupcontroller/plan_controller.go
  • api/backups/v1alpha1/plan_types.go
  • api/backups/v1alpha1/groupversion_info.go
  • api/backups/v1alpha1/restorejob_types.go
  • api/backups/v1alpha1/backupjob_types.go
  • api/backups/v1alpha1/backup_types.go
  • api/backups/v1alpha1/zz_generated.deepcopy.go
{go.mod,go.sum}

📄 CodeRabbit inference engine (AGENTS.md)

Do not manually modify go.mod and go.sum files; use go get command instead

Files:

  • go.mod
**/zz_generated.*.go

📄 CodeRabbit inference engine (AGENTS.md)

Do not edit auto-generated Go files matching pattern zz_generated.*.go

Files:

  • api/backups/v1alpha1/zz_generated.deepcopy.go
🧠 Learnings (3)
📚 Learning: 2025-11-27T11:21:45.808Z
Learnt from: CR
Repo: cozystack/cozystack PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T11:21:45.808Z
Learning: Applies to **/zz_generated.*.go : Do not edit auto-generated Go files matching pattern `zz_generated.*.go`

Applied to files:

  • api/.gitattributes
📚 Learning: 2025-11-27T11:21:45.808Z
Learnt from: CR
Repo: cozystack/cozystack PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T11:21:45.808Z
Learning: Applies to **/*.go : Use Controller-runtime patterns and kubebuilder style for Go code

Applied to files:

  • hack/update-codegen.sh
  • internal/backupcontroller/plan_controller.go
📚 Learning: 2025-07-11T06:11:25.438Z
Learnt from: lllamnyp
Repo: cozystack/cozystack PR: 1130
File: hack/e2e-apps/kubernetes.bats:101-101
Timestamp: 2025-07-11T06:11:25.438Z
Learning: In cozystack, the plural form for the Kubernetes custom resource is `kuberneteses.apps.cozystack.io`, not `kubernetes.apps.cozystack.io`. This is defined in the API schema even though it's not grammatically perfect.

Applied to files:

  • packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml
🧬 Code graph analysis (3)
internal/backupcontroller/factory/backupjob.go (2)
api/backups/v1alpha1/backupjob_types.go (2)
  • BackupJob (78-84)
  • BackupJobSpec (25-42)
api/backups/v1alpha1/plan_types.go (1)
  • Plan (24-29)
api/backups/v1alpha1/groupversion_info.go (3)
pkg/apiserver/apiserver.go (1)
  • Scheme (53-53)
pkg/apis/apps/v1alpha1/register.go (1)
  • addKnownTypes (56-59)
api/v1alpha1/cozystackresourcedefinitions_types.go (1)
  • init (43-45)
api/backups/v1alpha1/backupjob_types.go (1)
internal/backupcontroller/factory/backupjob.go (1)
  • BackupJob (12-28)
🪛 LanguageTool
api/backups/v1alpha1/DESIGN.md

[grammar] ~220-~220: Ensure spelling is correct
Context: ..., Running, Succeeded, Failed`. BackupJob contract with drivers * Core **creat...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[locale-violation] ~222-~222: In American English, ‘afterward’ is the preferred variant. ‘Afterwards’ is more commonly used in British English and other dialects.
Context: ...Joband must treatspec` as immutable afterwards. * Each driver controller: * Watches...

(AFTERWARDS_US)


[style] ~242-~242: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... * Set status.completedAt. * Set status.phase = Succeeded. 5. On fai...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~247-~247: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... * Set status.phase = Failed. * Set status.message and conditions. Drive...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~372-~372: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...* Set status.phase = Failed. * Set status.message and conditions. Drive...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🪛 markdownlint-cli2 (0.18.1)
api/backups/v1alpha1/DESIGN.md

98-98: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


138-138: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


164-164: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


168-168: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


184-184: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


205-205: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


220-220: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


261-261: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


274-274: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


286-286: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


319-319: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


332-332: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


344-344: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🔇 Additional comments (8)
api/.gitattributes (1)

1-1: LGTM!

Marking zz_generated_deepcopy.go as linguist-generated is the correct approach. This ensures GitHub's Linguist excludes auto-generated code from repository statistics and diff views, and signals that these files should not be manually edited.

go.mod (1)

7-31: New logging/metrics/cron dependencies look consistent with the backup controller usage

The added direct dependencies for logging (go-logr, zapr, zap), metrics (prometheus/client_golang), and scheduling (robfig/cron/v3) are coherent with the new backup controller functionality, and the versions look reasonable. Assuming these entries were introduced via standard Go tooling (go get, go mod tidy) and go.sum is in sync, there’s nothing blocking here.

Please double-check that:

  • All newly added packages are actually referenced in the codebase, and
  • go mod tidy (or equivalent) has been run so go.mod/go.sum remain consistent with the generated header.
packages/system/backup-controller/definitions/.gitattributes (1)

1-1: Marking all backup-controller definitions as generated is reasonable

Tagging everything under packages/system/backup-controller/definitions/ as linguist-generated matches the fact that these CRDs are produced by controller-gen/codegen. As long as you don’t plan to keep hand-maintained files in this directory, this is a sensible way to keep them out of language stats and reviews.

packages/system/backup-controller/definitions/backups.cozystack.io_backups.yaml (1)

1-234: Backup CRD schema aligns with the intended API surface

The backups.cozystack.io_backups.yaml CRD looks structurally sound and consistent with the described Backup API:

  • Spec correctly requires applicationRef, storageRef, strategyRef, and takenAt, with the reference shapes matching TypedLocalObjectReference/LocalObjectReference conventions.
  • Status models artifact, conditions, and a high-level phase in a way that’s compatible with standard tooling and UIs.
  • The schema appears to be standard controller-gen output with appropriate x-kubernetes-map-type: atomic annotations on references.

I don’t see any blocking issues in this CRD as written.

packages/system/backup-controller/definitions/backups.cozystack.io_restorejobs.yaml (1)

40-83: RestoreJob CRD schema looks consistent with the v1alpha1 types

backupRef is required, targetApplicationRef is optional but enforces kind/name when present, and the status/conditions shape matches the standard metav1.Condition. This lines up with the documented RestoreJobSpec/RestoreJobStatus contract.

Also applies to: 84-165

packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml (1)

41-131: BackupJob CRD aligns with the Go spec/status types

The CRD correctly requires applicationRef, storageRef, and strategyRef, treats planRef and backupRef as optional LocalObjectReferences, and uses a standard conditions schema. This matches the BackupJobSpec/BackupJobStatus definitions and the intended contract.

Also applies to: 132-228

api/backups/v1alpha1/zz_generated.deepcopy.go (1)

1-478: Generated deepcopy implementations look correct; keep as-is

This file is clearly controller-gen output and the DeepCopy/DeepCopyObject implementations for the new backups types follow the usual patterns. No manual changes are needed here; just regenerate via your codegen script if the API types change.

internal/backupcontroller/plan_controller.go (1)

44-52: Handle invalid or empty cron expressions to avoid "epoch 0" jobs

If p.Spec.Schedule.Cron is empty or invalid, nextCronAfter returns a zero time.Time, and the reconciler treats the Plan as due and creates a BackupJob scheduled at the Unix epoch. This repeats on every reconcile until the job name collides, with no clear signal to the user that the schedule is misconfigured.

Change nextCronAfter to propagate parse errors explicitly:

-func nextCronAfter(c string, t time.Time) time.Time {
-	schedule, err := cron.ParseStandard(c)
-	if err != nil {
-		return time.Time{}
-	}
-	return schedule.Next(t)
-}
+func nextCronAfter(c string, t time.Time) (time.Time, error) {
+	schedule, err := cron.ParseStandard(c)
+	if err != nil {
+		return time.Time{}, err
+	}
+	return schedule.Next(t), nil
+}

and handle the error in Reconcile:

-	tCheck := time.Now().Add(-startingDeadlineSeconds)
-	tNext := nextCronAfter(p.Spec.Schedule.Cron, tCheck)
+	tCheck := time.Now().Add(-startingDeadlineSeconds)
+	tNext, err := nextCronAfter(p.Spec.Schedule.Cron, tCheck)
+	if err != nil {
+		log.Error(err, "invalid cron expression on Plan", "cron", p.Spec.Schedule.Cron)
+		return ctrl.Result{RequeueAfter: startingDeadlineSeconds}, nil
+	}

This surfaces configuration errors via logs instead of silently generating malformed jobs.

Comment on lines +68 to +82
schedule:
description: Schedule specifies when backup copies are created.
properties:
cron:
description: |-
Cron contains the cron spec for scheduling backups. Must be
specified if the schedule type is `cron`. Since only `cron` is
supported, omitting this field is not allowed.
type: string
type:
description: |-
Type is the type of schedule specification. Supported values are
[`cron`]. If omitted, defaults to `cron`.
type: string
type: object
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Enforce spec.schedule.cron and restrict spec.schedule.type to match controller behavior

Right now the CRD allows spec.schedule objects without a cron field, and type is an unconstrained string. The description says cron is mandatory and only cron is supported, but the schema does not enforce that. Combined with the controller’s use of p.Spec.Schedule.Cron and nextCronAfter, this means a Plan with a missing or invalid cron will be accepted by the API and later cause the controller to treat it as due with a zero-time schedule.

I recommend tightening the schema so misconfigured Plans fail fast at admission instead of producing “epoch 0” jobs:

  • Under spec.properties.schedule:
    • Add required: ["cron"] (and optionally "type" if you want to forbid unknown schedule kinds).
    • Add an enum on type (e.g. enum: ["", "cron"]) or just remove the field until more schedule types exist.
  • Optionally add format/pattern validation for cron or a comment noting it must be a robfig/cron v3 standard expression.

Ideally this should be done via kubebuilder markers on PlanSchedule in api/backups/v1alpha1/plan_types.go and re-running controller-gen, rather than editing the YAML directly.

Also applies to: 127-131

@lllamnyp lllamnyp added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:XXL This PR changes 1000+ lines, ignoring generated files. labels Dec 4, 2025
@lllamnyp
Copy link
Member Author

lllamnyp commented Dec 4, 2025

@dosubot refresh

@dosubot
Copy link

dosubot bot commented Dec 4, 2025

Here’s the latest on the backup feature:

  • The new backups.cozystack.io API group and a cronjob-like controller for backup jobs are now part of Cozystack core. This system supports scheduled backup plans, job execution, artifact management, and restore operations. You can define backup plans with cron scheduling, track jobs through multiple lifecycle phases, and manage restores from stored backups (PR #1640).
  • Deployment work is ongoing: there’s an open PR to add build steps and a Helm chart for the backup controller, so it can be deployed as part of the PaaS bundles (PR #1685).
  • Documentation is up-to-date for backup and recovery, including how to use Velero for Kubernetes resource backups and volume snapshots, and how to configure S3-compatible storage and secrets (docs).
  • For managed PostgreSQL, backups are enabled by configuring S3 storage and setting backup parameters. Recovery uses bootstrap config for point-in-time restore (docs).
  • There’s an open issue about a double template key in the Postgres backup cronjob YAML, which can cause Helm install failures for backups. The team is aware and working on it (issue #869).

Let me know if you want technical details or guidance on configuring or troubleshooting backups!

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

## What this PR does

This patch creates a new backups.cozystack.io API group which includes
backup-related resources owned exclusively by Cozystack core. A
cronjob-like controller is implemented to create backup jobs that will
be handled by appropriate third-party or external controllers.

### Release note

```release-note
[backups] Implement the core backup API and an accompanying controller.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
@lllamnyp lllamnyp force-pushed the feat/backup-controller branch from 47c4655 to a7b4239 Compare December 5, 2025 08:58
@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:XL This PR changes 500-999 lines, ignoring generated files. labels Dec 5, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
api/backups/v1alpha1/plan_types.go (1)

66-77: Align PlanSchedule comments, validation, and controller behavior for type/cron.

Cron is tagged +optional and the schema doesn’t require it, but the comment says omitting it “is not allowed”, and the controller unconditionally parses p.Spec.Schedule.Cron. Similarly, Type is a free-form string while comments say only cron is supported and it “defaults to cron”, but there’s no defaulting logic here.

To make this consistent and fail fast:

  • Either:

    • Make cron required in the schema and remove +optional, and
    • Add an enum validation on Type (e.g. "" and "cron") or drop the field until more kinds exist,
  • Or soften the comments to reflect that:

    • Both fields are optional at the API level,
    • Invalid/missing cron and unsupported Type values are rejected by runtime validation/webhooks.

Given you already parse Schedule.Cron in PlanReconciler, tightening the schema (via kubebuilder markers here then re-running controller-gen) would keep users from submitting unusable Plans.

api/backups/v1alpha1/groupversion_info.go (1)

39-53: Scheme registration for backup API types looks correct now.

addKnownTypes now explicitly registers all CRD types (Plan, Backup, BackupJob, RestoreJob and their list variants) against GroupVersion, so AddToScheme will work with controller-runtime clients as expected. This addresses the earlier concern about AddKnownTypes being a no-op.

packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml (1)

68-82: Tighten spec.schedule schema to match comments and controller behavior.

The CRD currently allows schedule objects without cron, and type is an unconstrained string, but the descriptions say cron is mandatory and only cron is supported. Given the controller always reads spec.schedule.cron, this mismatch can let users submit Plans that will never schedule successfully.

Recommend:

  • Under spec.properties.schedule, add a required: ["cron"] (and optionally "type" if you want to reject unknown schedule kinds), and
  • Restrict type with an enum (e.g. ["", "cron"]) or remove it until more schedule types exist.

Best to express this via kubebuilder markers on PlanSchedule in plan_types.go and then regenerate this CRD with controller-gen, rather than editing the YAML directly.

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

20-23: Fix typo and consider more specific condition type name.

The // Condtions comment has a typo, and PlanConditionError = "Error" is quite generic as a condition type. Consider renaming the constant to something like "ScheduleError" (and updating call sites) to better convey what the condition represents.

api/backups/v1alpha1/backup_types.go (1)

84-102: Consider enabling the status subresource for Backup.

BackupStatus is modeled for controller updates, but Backup lacks a +kubebuilder:subresource:status marker. If drivers or controllers will update .status via client.Status().Update, it’s more idiomatic (and safer) to expose a status subresource.

You can add:

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
type Backup struct {
    ...
}

and re-run controller-gen to update the CRD.

api/backups/v1alpha1/restorejob_types.go (1)

62-80: Optionally expose a status subresource for RestoreJob.

RestoreJobStatus is intended to be driven by a controller, but RestoreJob doesn’t declare +kubebuilder:subresource:status. If a restore controller will update status via the status client, consider adding the marker so it doesn’t need full-object updates:

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
type RestoreJob struct {
    ...
}

Then regenerate the CRD.

api/backups/v1alpha1/backupjob_types.go (1)

74-93: Expose BackupJob status as a subresource for controller updates.

BackupJobStatus is designed for lifecycle reporting, but BackupJob doesn’t declare +kubebuilder:subresource:status. For controller-runtime style reconcilers that call client.Status().Update, it’s conventional to expose a status subresource:

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
type BackupJob struct {
    ...
}

Then re-run controller-gen to refresh the CRD.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47c4655 and a7b4239.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (17)
  • api/.gitattributes (1 hunks)
  • api/backups/v1alpha1/DESIGN.md (1 hunks)
  • api/backups/v1alpha1/backup_types.go (1 hunks)
  • api/backups/v1alpha1/backupjob_types.go (1 hunks)
  • api/backups/v1alpha1/groupversion_info.go (1 hunks)
  • api/backups/v1alpha1/plan_types.go (1 hunks)
  • api/backups/v1alpha1/restorejob_types.go (1 hunks)
  • api/backups/v1alpha1/zz_generated.deepcopy.go (1 hunks)
  • go.mod (1 hunks)
  • hack/update-codegen.sh (2 hunks)
  • internal/backupcontroller/factory/backupjob.go (1 hunks)
  • internal/backupcontroller/plan_controller.go (1 hunks)
  • packages/system/backup-controller/definitions/.gitattributes (1 hunks)
  • packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml (1 hunks)
  • packages/system/backup-controller/definitions/backups.cozystack.io_backups.yaml (1 hunks)
  • packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml (1 hunks)
  • packages/system/backup-controller/definitions/backups.cozystack.io_restorejobs.yaml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • api/.gitattributes
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/backupcontroller/factory/backupjob.go
  • hack/update-codegen.sh
  • packages/system/backup-controller/definitions/backups.cozystack.io_backups.yaml
  • packages/system/backup-controller/definitions/.gitattributes
🧰 Additional context used
📓 Path-based instructions (3)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Use Controller-runtime patterns and kubebuilder style for Go code

Files:

  • api/backups/v1alpha1/groupversion_info.go
  • api/backups/v1alpha1/plan_types.go
  • api/backups/v1alpha1/backup_types.go
  • api/backups/v1alpha1/restorejob_types.go
  • internal/backupcontroller/plan_controller.go
  • api/backups/v1alpha1/backupjob_types.go
  • api/backups/v1alpha1/zz_generated.deepcopy.go
{go.mod,go.sum}

📄 CodeRabbit inference engine (AGENTS.md)

Do not manually modify go.mod and go.sum files; use go get command instead

Files:

  • go.mod
**/zz_generated.*.go

📄 CodeRabbit inference engine (AGENTS.md)

Do not edit auto-generated Go files matching pattern zz_generated.*.go

Files:

  • api/backups/v1alpha1/zz_generated.deepcopy.go
🧠 Learnings (2)
📚 Learning: 2025-11-27T11:21:45.808Z
Learnt from: CR
Repo: cozystack/cozystack PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T11:21:45.808Z
Learning: Applies to **/*.go : Use Controller-runtime patterns and kubebuilder style for Go code

Applied to files:

  • api/backups/v1alpha1/plan_types.go
📚 Learning: 2025-07-11T06:11:25.438Z
Learnt from: lllamnyp
Repo: cozystack/cozystack PR: 1130
File: hack/e2e-apps/kubernetes.bats:101-101
Timestamp: 2025-07-11T06:11:25.438Z
Learning: In cozystack, the plural form for the Kubernetes custom resource is `kuberneteses.apps.cozystack.io`, not `kubernetes.apps.cozystack.io`. This is defined in the API schema even though it's not grammatically perfect.

Applied to files:

  • packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml
🧬 Code graph analysis (1)
api/backups/v1alpha1/backupjob_types.go (1)
internal/backupcontroller/factory/backupjob.go (1)
  • BackupJob (12-28)
🪛 LanguageTool
api/backups/v1alpha1/DESIGN.md

[grammar] ~220-~220: Ensure spelling is correct
Context: ..., Running, Succeeded, Failed`. BackupJob contract with drivers * Core **creat...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[locale-violation] ~222-~222: In American English, ‘afterward’ is the preferred variant. ‘Afterwards’ is more commonly used in British English and other dialects.
Context: ...Joband must treatspec` as immutable afterwards. * Each driver controller: * Watches...

(AFTERWARDS_US)


[style] ~242-~242: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... * Set status.completedAt. * Set status.phase = Succeeded. 5. On fai...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~247-~247: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... * Set status.phase = Failed. * Set status.message and conditions. Drive...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~372-~372: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...* Set status.phase = Failed. * Set status.message and conditions. Drive...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🪛 markdownlint-cli2 (0.18.1)
api/backups/v1alpha1/DESIGN.md

98-98: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


138-138: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


164-164: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


168-168: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


184-184: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


205-205: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


220-220: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


261-261: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


274-274: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


286-286: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


319-319: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


332-332: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


344-344: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🔇 Additional comments (6)
go.mod (1)

7-31: Dependencies align with new controller and logging usage.

The added direct requirements (logr/zapr, zap, prometheus client, cron/v3) match the new Plan controller and expected logging/metrics patterns. No issues from a module perspective.

internal/backupcontroller/plan_controller.go (1)

64-83: Confirm requeue strategy vs desired cron fidelity.

After firing a run (time.Now().After(tNext)), you always requeue after startingDeadlineSeconds (300s), and on IsAlreadyExists you do the same. Combined with tCheck := now - startingDeadlineSeconds, this means:

  • You'll create at most one BackupJob per 5-minute interval, even if the cron expression is more frequent.
  • Missed runs beyond the 5-minute look-back window are intentionally ignored.

This is a deliberate trade-off: the controller bounds scheduling to reduce catch-up complexity. If the goal is "approximate cron with a bounded look-back window," this is appropriate. If you want closer adherence for frequent schedules (e.g., every minute), consider requeueing to the next cron time instead (e.g., sch.Next(time.Now())) or clamping RequeueAfter to min(nextCronDelay, startingDeadlineSeconds). DESIGN.md intentionally leaves this implementation detail unspecified, so verify against your intended scheduling semantics.

packages/system/backup-controller/definitions/backups.cozystack.io_restorejobs.yaml (1)

1-168: LGTM! RestoreJob CRD is well-structured.

The CRD definition follows Kubernetes conventions correctly with proper validation constraints, required fields, atomic map types, and standard condition patterns. The schema aligns with the design document specifications.

packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml (1)

1-231: LGTM! BackupJob CRD is well-defined.

The CRD correctly implements the BackupJob contract described in the design document. Required fields (applicationRef, storageRef, strategyRef) are properly marked, planRef is correctly optional for ad-hoc backups, and the status structure follows Kubernetes conventions with proper phase and condition tracking.

api/backups/v1alpha1/zz_generated.deepcopy.go (1)

1-478: LGTM! Autogenerated deepcopy code is correct.

The generated deepcopy implementations follow controller-runtime conventions correctly with proper nil checks, deep copying of slices and maps, and correct handling of TypeMeta/ObjectMeta. As per coding guidelines, this file should not be manually edited.

api/backups/v1alpha1/DESIGN.md (1)

128-136: Fix missing backticks in struct tag documentation.

The Go struct tags in the code example are missing backticks. Line 131 should show `json:"type,omitempty"` (with backticks) instead of json:"type,omitempty" (without backticks). Same issue on line 134.

Apply this diff:

 type PlanSchedule struct {
     // Type is the schedule type. Currently only "cron" is supported.
     // Defaults to "cron".
-    Type PlanScheduleType `json:"type,omitempty"`
+    Type PlanScheduleType `json:"type,omitempty"`
 
     // Cron expression (required for cron type).
-    Cron string `json:"cron,omitempty"`
+    Cron string `json:"cron,omitempty"`
 }

Likely an incorrect or invalid review comment.

Comment on lines +48 to +63
tCheck := time.Now().Add(-startingDeadlineSeconds)
sch, err := cron.ParseStandard(p.Spec.Schedule.Cron)
if err != nil {
errWrapped := fmt.Errorf("could not parse cron %s: %w", p.Spec.Schedule.Cron, err)
log.Error(err, "could not parse cron", "cron", p.Spec.Schedule.Cron)
meta.SetStatusCondition(&p.Status.Conditions, metav1.Condition{
Type: backupsv1alpha1.PlanConditionError,
Status: metav1.ConditionTrue,
Reason: "Failed to parse cron spec",
Message: errWrapped.Error(),
})
if err := r.Status().Update(ctx, p); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clear or flip PlanConditionError once the cron spec becomes valid.

On parse failure you set PlanConditionError to True and update status, but there’s no corresponding path that marks it False or removes it after the user fixes spec.schedule.cron. That means a Plan can carry a permanent error condition even though subsequent reconciles succeed.

Consider, on a successful parse, either:

  • Setting the same condition with Status: False and an appropriate Reason/Message, or
  • Removing the condition entirely if you only care about active errors.

This keeps .status.conditions aligned with the current Plan state.

Copy link
Member

@kvaps kvaps left a comment

Choose a reason for hiding this comment

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

LGTM

@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Dec 5, 2025
@lllamnyp lllamnyp merged commit 67ecf3d into main Dec 5, 2025
28 of 30 checks passed
@lllamnyp lllamnyp deleted the feat/backup-controller branch December 5, 2025 20:15
@lllamnyp lllamnyp mentioned this pull request Dec 11, 2025
19 tasks
lllamnyp added a commit that referenced this pull request Jan 8, 2026
## What this PR does

This patch creates a new backups.cozystack.io API group which includes
backup-related resources owned exclusively by Cozystack core. A
cronjob-like controller is implemented to create backup jobs that will
be handled by appropriate third-party or external controllers.

### Release note

```release-note
[backups] Implement the core backup API and an accompanying controller.
```

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Adds a full backups system: create scheduled Plans (cron), run
BackupJobs with lifecycle phases (Pending→Running→Succeeded/Failed), and
produce Backup artifacts with metadata.
* Adds RestoreJobs to restore from stored Backups and track restore
progress/status.
* Exposes new API resources (Plan, BackupJob, Backup, RestoreJob) so
backups and restores can be declared and observed via the platform.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

(cherry picked from commit 67ecf3d)
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
kvaps added a commit that referenced this pull request Jan 16, 2026
…d backup system (#1867)

## What this PR does

Update changelog for v1.0.0-alpha.1 to include missing features:
- **Cozystack Operator**: New operator for Package and PackageSource
management (#1740, #1741, #1755, #1756, #1760, #1761)
- **Backup System**: Comprehensive backup functionality with Velero
integration (#1640, #1685, #1687, #1708, #1719, #1720, #1737, #1762)
- Add @androndo to contributors
- Update Full Changelog link to v0.38.0...v1.0.0-alpha.1

### Release note

```release-note
[docs] Update changelog for v1.0.0-alpha.1: add cozystack-operator and backup system
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request lgtm This PR has been approved by a maintainer size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants