Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions api/backups/v1alpha1/backup_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,6 @@ type BackupSpec struct {
// +optional
PlanRef *corev1.LocalObjectReference `json:"planRef,omitempty"`

// StorageRef refers to the Storage object that describes where the backup
// artifact is stored.
StorageRef corev1.TypedLocalObjectReference `json:"storageRef"`

// StrategyRef refers to the driver-specific BackupStrategy that was used
// to create this backup. This allows the driver to later perform restores.
StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"`
Expand Down
85 changes: 85 additions & 0 deletions api/backups/v1alpha1/backupclass_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: Apache-2.0
// Package v1alpha1 defines backups.cozystack.io API types.
//
// Group: backups.cozystack.io
// Version: v1alpha1
package v1alpha1

import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)

func init() {
SchemeBuilder.Register(func(s *runtime.Scheme) error {
s.AddKnownTypes(GroupVersion,
&BackupClass{},
&BackupClassList{},
)
return nil
})
}

// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster
// +kubebuilder:subresource:status

// BackupClass defines a class of backup configurations that can be referenced
// by BackupJob and Plan resources. It encapsulates strategy and storage configuration
// per application type.
type BackupClass struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec BackupClassSpec `json:"spec,omitempty"`
Status BackupClassStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// BackupClassList contains a list of BackupClasses.
type BackupClassList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []BackupClass `json:"items"`
}

// BackupClassSpec defines the desired state of a BackupClass.
type BackupClassSpec struct {
// Strategies is a list of backup strategies, each matching a specific application type.
Strategies []BackupClassStrategy `json:"strategies"`
}

// BackupClassStrategy defines a backup strategy for a specific application type.
type BackupClassStrategy struct {
// StrategyRef references the driver-specific BackupStrategy (e.g., Velero).
StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"`

// Application specifies which application types this strategy applies to.
Application ApplicationSelector `json:"application"`

// Parameters holds strategy-specific and storage-specific parameters.
// Common parameters include:
// - backupStorageLocationName: Name of Velero BackupStorageLocation
// +optional
Parameters map[string]string `json:"parameters,omitempty"`
}

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

// Kind is the kind of the application (e.g., VirtualMachine, MySQL).
Kind string `json:"kind"`
}

// BackupClassStatus defines the observed state of a BackupClass.
type BackupClassStatus struct {
// Conditions represents the latest available observations of a BackupClass's state.
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
12 changes: 5 additions & 7 deletions api/backups/v1alpha1/backupjob_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,13 @@ type BackupJobSpec struct {

// ApplicationRef holds a reference to the managed application whose state
// is being backed up.
// If apiGroup is not specified, it defaults to "apps.cozystack.io".
ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"`

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

// StrategyRef holds a reference to the driver-specific BackupStrategy object
// that describes how the backup should be created.
StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"`
// BackupClassName references a BackupClass that contains strategy and storage configuration.
// The BackupClass will be resolved to determine the appropriate strategy and storage
// based on the ApplicationRef.
BackupClassName string `json:"backupClassName"`
}

// BackupJobStatus represents the observed state of a BackupJob.
Expand Down
12 changes: 5 additions & 7 deletions api/backups/v1alpha1/plan_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,13 @@ type PlanList struct {
type PlanSpec struct {
// ApplicationRef holds a reference to the managed application,
// whose state and configuration must be backed up.
// If apiGroup is not specified, it defaults to "apps.cozystack.io".
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"`
// BackupClassName references a BackupClass that contains strategy and storage configuration.
// The BackupClass will be resolved to determine the appropriate strategy and storage
// based on the ApplicationRef.
BackupClassName string `json:"backupClassName"`

// Schedule specifies when backup copies are created.
Schedule PlanSchedule `json:"schedule"`
Expand Down
152 changes: 147 additions & 5 deletions api/backups/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

78 changes: 78 additions & 0 deletions internal/backupcontroller/backupclass_resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package backupcontroller

import (
"context"
"fmt"

corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1"
)

const (
// DefaultApplicationAPIGroup is the default API group for applications
// when not specified in ApplicationRef or ApplicationSelector.
DefaultApplicationAPIGroup = "apps.cozystack.io"
)

// NormalizeApplicationRef sets the default apiGroup to "apps.cozystack.io" if it's not specified.
// This function is exported so it can be used by other packages (e.g., factory).
func NormalizeApplicationRef(ref corev1.TypedLocalObjectReference) corev1.TypedLocalObjectReference {
if ref.APIGroup == nil || *ref.APIGroup == "" {
apiGroup := DefaultApplicationAPIGroup
ref.APIGroup = &apiGroup
}
return ref
}

// ResolvedBackupConfig contains the resolved strategy and storage configuration
// from a BackupClass.
type ResolvedBackupConfig struct {
StrategyRef corev1.TypedLocalObjectReference
Parameters map[string]string
}

// ResolveBackupClass resolves a BackupClass and finds the matching strategy for the given application.
// It normalizes the applicationRef's apiGroup (defaults to apps.cozystack.io if not specified)
// and matches it against the strategies in the BackupClass.
func ResolveBackupClass(
ctx context.Context,
c client.Client,
backupClassName string,
applicationRef corev1.TypedLocalObjectReference,
) (*ResolvedBackupConfig, error) {
// Normalize applicationRef (default apiGroup if not specified)
applicationRef = NormalizeApplicationRef(applicationRef)

// Get BackupClass
backupClass := &backupsv1alpha1.BackupClass{}
if err := c.Get(ctx, client.ObjectKey{Name: backupClassName}, backupClass); err != nil {
return nil, fmt.Errorf("failed to get BackupClass %s: %w", backupClassName, err)
}

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

// Find matching strategy
for _, strategy := range backupClass.Spec.Strategies {
// Normalize strategy's application selector (default apiGroup if not specified)
strategyAPIGroup := DefaultApplicationAPIGroup
if strategy.Application.APIGroup != nil && *strategy.Application.APIGroup != "" {
strategyAPIGroup = *strategy.Application.APIGroup
}

if strategyAPIGroup == appAPIGroup && strategy.Application.Kind == applicationRef.Kind {
return &ResolvedBackupConfig{
StrategyRef: strategy.StrategyRef,
Parameters: strategy.Parameters,
}, nil
}
}

return nil, fmt.Errorf("no matching strategy found in BackupClass %s for application %s/%s",
backupClassName, appAPIGroup, applicationRef.Kind)
}
Loading