-
Notifications
You must be signed in to change notification settings - Fork 136
[backups] Implement core backup Plan controller #1640
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
3a2c842 to
9fcb2cc
Compare
c70a53a to
cb24e7f
Compare
cb24e7f to
47c4655
Compare
There was a problem hiding this 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"andownerReferencesset. 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
crontype is currently supported. However, thecronfield is not in therequiredlist 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: objectAlternatively, implement validation via a CEL rule or validating webhook if you want to support future schedule types where
cronis 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
subresourcesfield 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:
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.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
subresourcesfield 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
subresourcesfield 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
⛔ Files ignored due to path filters (1)
go.sumis 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.goapi/backups/v1alpha1/groupversion_info.gointernal/backupcontroller/plan_controller.goapi/backups/v1alpha1/backup_types.gointernal/backupcontroller/factory/backupjob.goapi/backups/v1alpha1/restorejob_types.goapi/backups/v1alpha1/backupjob_types.goapi/backups/v1alpha1/zz_generated.deepcopy.go
{go.mod,go.sum}
📄 CodeRabbit inference engine (AGENTS.md)
Do not manually modify
go.modandgo.sumfiles; usego getcommand 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*.yamlat 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
addKnownTypesfunction correctly sets up the group version registration. The empty type list inscheme.AddKnownTypesis expected, as types should be registered viaSchemeBuilder.Register()calls ininit()functions within their respective type definition files (e.g.,plan_types.go,backupjob_types.go, etc.).Note: I've flagged the missing
init()function inplan_types.goseparately. 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 changesThis 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.*.goguideline.
| // +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"` | ||
| } |
There was a problem hiding this comment.
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.
| // +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"` | ||
| } |
There was a problem hiding this comment.
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.
| // 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"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
- Removing the
+optionalmarker and making it required at the schema level, or - 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.
| // +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"` | ||
| } |
There was a problem hiding this comment.
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.
| func nextCronAfter(c string, t time.Time) time.Time { | ||
| schedule, err := cron.ParseStandard(c) | ||
| if err != nil { | ||
| return time.Time{} | ||
| } | ||
| return schedule.Next(t) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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).
There was a problem hiding this 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 validationThe 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
.statuswithout racing on.spec:// +kubebuilder:object:root=true // +kubebuilder:subresource:status type RestoreJob struct { ... }Tighten the CRD schema for
Phaseby 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 formatThe factory correctly:
- Derives namespace/name from the Plan and scheduled time.
- Sets
PlanRefand copiesApplicationRef/StorageRef/StrategyReffrom the Plan spec, avoiding accidental aliasing viaDeepCopy().One small optional improvement: the
scheduledFor.Unix()/60naming 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-HHMMin 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 globsThe new
TMPDIRworkflow and split of CRDs into dedicated directories is a nice improvement, but withset -o errexitthe unguardedmvcalls 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
mvexits non‑zero.To make this more robust, you can enable
nullgloband 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}/" +fiThis 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 docsThe
Plan,PlanSpec, andPlanScheduletypes 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
cronand that omittingTypedefaults it tocron, but there is nodefaultorEnummarker.PlanSchedule.Cron:
- Comment says “omitting this field is not allowed”, but the field is marked
+optionalandomitempty.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 useminRequeueDelay
minRequeueDelayis declared but never used; onlystartingDeadlineSecondsparticipates in requeue decisions. Either wireminRequeueDelayinto the scheduling logic or drop the constant to avoid dead code.api/backups/v1alpha1/backup_types.go (1)
68-83: Consider adding+kubebuilder:subresource:statustoBackup
BackupStatusis clearly controller-owned (phase, artifact, conditions), butBackupcurrently 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
/statuswithout 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 subresourceTwo small inconsistencies worth addressing:
- DESIGN.md documents a
BackupJobSpecfield:// Informational: what triggered this run ("Plan", "Manual", etc.). TriggeredBy string `json:"triggeredBy,omitempty"`but the actual
BackupJobSpec(and the CRD) do not includeTriggeredBy. Either:
- add
TriggeredBytoBackupJobSpecand regenerate the CRD, or- update DESIGN.md to drop that field if you intentionally decided against it.
- As with
Backup,BackupJobhas a controller-updatedStatusbut 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 itSections 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
⛔ Files ignored due to path filters (1)
go.sumis 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.gointernal/backupcontroller/plan_controller.goapi/backups/v1alpha1/plan_types.goapi/backups/v1alpha1/groupversion_info.goapi/backups/v1alpha1/restorejob_types.goapi/backups/v1alpha1/backupjob_types.goapi/backups/v1alpha1/backup_types.goapi/backups/v1alpha1/zz_generated.deepcopy.go
{go.mod,go.sum}
📄 CodeRabbit inference engine (AGENTS.md)
Do not manually modify
go.modandgo.sumfiles; usego getcommand 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.shinternal/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.goaslinguist-generatedis 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 usageThe 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) andgo.sumis 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 sogo.mod/go.sumremain consistent with the generated header.packages/system/backup-controller/definitions/.gitattributes (1)
1-1: Marking all backup-controller definitions as generated is reasonableTagging everything under
packages/system/backup-controller/definitions/aslinguist-generatedmatches the fact that these CRDs are produced bycontroller-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 surfaceThe
backups.cozystack.io_backups.yamlCRD looks structurally sound and consistent with the describedBackupAPI:
- Spec correctly requires
applicationRef,storageRef,strategyRef, andtakenAt, with the reference shapes matchingTypedLocalObjectReference/LocalObjectReferenceconventions.- Status models
artifact,conditions, and a high-levelphasein 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: atomicannotations 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
backupRefis required,targetApplicationRefis optional but enforceskind/namewhen present, and the status/conditions shape matches the standardmetav1.Condition. This lines up with the documentedRestoreJobSpec/RestoreJobStatuscontract.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 typesThe CRD correctly requires
applicationRef,storageRef, andstrategyRef, treatsplanRefandbackupRefas optionalLocalObjectReferences, and uses a standardconditionsschema. This matches theBackupJobSpec/BackupJobStatusdefinitions 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-isThis 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" jobsIf
p.Spec.Schedule.Cronis empty or invalid,nextCronAfterreturns a zerotime.Time, and the reconciler treats the Plan as due and creates aBackupJobscheduled 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
nextCronAfterto 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.
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
- Add
- Optionally add format/pattern validation for
cronor 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
|
@dosubot refresh |
|
Here’s the latest on the backup feature:
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 |
## 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>
47c4655 to
a7b4239
Compare
There was a problem hiding this 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: AlignPlanSchedulecomments, validation, and controller behavior fortype/cron.
Cronis tagged+optionaland the schema doesn’t require it, but the comment says omitting it “is not allowed”, and the controller unconditionally parsesp.Spec.Schedule.Cron. Similarly,Typeis a free-form string while comments say onlycronis supported and it “defaults tocron”, but there’s no defaulting logic here.To make this consistent and fail fast:
Either:
- Make
cronrequired 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
Typevalues are rejected by runtime validation/webhooks.Given you already parse
Schedule.CroninPlanReconciler, 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.
addKnownTypesnow explicitly registers all CRD types (Plan,Backup,BackupJob,RestoreJoband their list variants) againstGroupVersion, soAddToSchemewill work with controller-runtime clients as expected. This addresses the earlier concern aboutAddKnownTypesbeing a no-op.packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml (1)
68-82: Tightenspec.scheduleschema to match comments and controller behavior.The CRD currently allows
scheduleobjects withoutcron, andtypeis an unconstrained string, but the descriptions say cron is mandatory and onlycronis supported. Given the controller always readsspec.schedule.cron, this mismatch can let users submit Plans that will never schedule successfully.Recommend:
- Under
spec.properties.schedule, add arequired: ["cron"](and optionally"type"if you want to reject unknown schedule kinds), and- Restrict
typewith an enum (e.g.["", "cron"]) or remove it until more schedule types exist.Best to express this via kubebuilder markers on
PlanScheduleinplan_types.goand then regenerate this CRD withcontroller-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
// Condtionscomment has a typo, andPlanConditionError = "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 forBackup.
BackupStatusis modeled for controller updates, butBackuplacks a+kubebuilder:subresource:statusmarker. If drivers or controllers will update.statusviaclient.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-gento update the CRD.api/backups/v1alpha1/restorejob_types.go (1)
62-80: Optionally expose a status subresource forRestoreJob.
RestoreJobStatusis intended to be driven by a controller, butRestoreJobdoesn’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: ExposeBackupJobstatus as a subresource for controller updates.
BackupJobStatusis designed for lifecycle reporting, butBackupJobdoesn’t declare+kubebuilder:subresource:status. For controller-runtime style reconcilers that callclient.Status().Update, it’s conventional to expose a status subresource:// +kubebuilder:object:root=true // +kubebuilder:subresource:status type BackupJob struct { ... }Then re-run
controller-gento refresh the CRD.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis 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.goapi/backups/v1alpha1/plan_types.goapi/backups/v1alpha1/backup_types.goapi/backups/v1alpha1/restorejob_types.gointernal/backupcontroller/plan_controller.goapi/backups/v1alpha1/backupjob_types.goapi/backups/v1alpha1/zz_generated.deepcopy.go
{go.mod,go.sum}
📄 CodeRabbit inference engine (AGENTS.md)
Do not manually modify
go.modandgo.sumfiles; usego getcommand 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 afterstartingDeadlineSeconds(300s), and onIsAlreadyExistsyou do the same. Combined withtCheck := now - startingDeadlineSeconds, this means:
- You'll create at most one
BackupJobper 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 clampingRequeueAftertomin(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 ofjson:"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.
| 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 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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: Falseand an appropriateReason/Message, or - Removing the condition entirely if you only care about active errors.
This keeps .status.conditions aligned with the current Plan state.
kvaps
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
## 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>
…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 ```
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
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.