[backups]

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
This commit is contained in:
Timofei Larkin 2026-02-03 21:39:15 +03:00
parent 3eaadfc95c
commit 000b5ff76c
17 changed files with 481 additions and 488 deletions

View file

@ -55,8 +55,9 @@ type VeleroSpec struct {
// VeleroTemplate describes the data a backup.velero.io should have when
// templated from a Velero backup strategy.
type VeleroTemplate struct {
Spec velerov1.BackupSpec `json:"spec"`
RestoreSpec velerov1.RestoreSpec `json:"restoreSpec"`
Spec velerov1.BackupSpec `json:"spec"`
// +optional
RestoreSpec *velerov1.RestoreSpec `json:"restoreSpec,omitempty"`
}
type VeleroStatus struct {

View file

@ -21,6 +21,7 @@ limitations under the License.
package v1alpha1
import (
velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
@ -223,6 +224,11 @@ func (in *VeleroStatus) DeepCopy() *VeleroStatus {
func (in *VeleroTemplate) DeepCopyInto(out *VeleroTemplate) {
*out = *in
in.Spec.DeepCopyInto(&out.Spec)
if in.RestoreSpec != nil {
in, out := &in.RestoreSpec, &out.RestoreSpec
*out = new(velerov1.RestoreSpec)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VeleroTemplate.

View file

@ -57,6 +57,7 @@ type BackupJobSpec struct {
// The BackupClass will be resolved to determine the appropriate strategy and storage
// based on the ApplicationRef.
// This field is immutable once the BackupJob is created.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="backupClassName is immutable"
BackupClassName string `json:"backupClassName"`
}

View file

@ -1,67 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"context"
"fmt"
"strings"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
// SetupWebhookWithManager registers the BackupJob webhook with the manager.
func SetupBackupJobWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(&BackupJob{}).
Complete()
}
// +kubebuilder:webhook:path=/mutate-backups-cozystack-io-v1alpha1-backupjob,mutating=true,failurePolicy=fail,sideEffects=None,groups=backups.cozystack.io,resources=backupjobs,verbs=create;update,versions=v1alpha1,name=mbackupjob.kb.io,admissionReviewVersions=v1
// Default implements webhook.Defaulter so a webhook will be registered for the type
func (j *BackupJob) Default() {
j.Spec.ApplicationRef = NormalizeApplicationRef(j.Spec.ApplicationRef)
}
// +kubebuilder:webhook:path=/validate-backups-cozystack-io-v1alpha1-backupjob,mutating=false,failurePolicy=fail,sideEffects=None,groups=backups.cozystack.io,resources=backupjobs,verbs=create;update,versions=v1alpha1,name=vbackupjob.kb.io,admissionReviewVersions=v1
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (j *BackupJob) ValidateCreate() (admission.Warnings, error) {
logger := log.FromContext(context.Background())
logger.Info("validating BackupJob creation", "name", j.Name, "namespace", j.Namespace)
// Validate that backupClassName is set
if strings.TrimSpace(j.Spec.BackupClassName) == "" {
return nil, fmt.Errorf("backupClassName is required and cannot be empty")
}
return nil, nil
}
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (j *BackupJob) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
logger := log.FromContext(context.Background())
logger.Info("validating BackupJob update", "name", j.Name, "namespace", j.Namespace)
oldJob, ok := old.(*BackupJob)
if !ok {
return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a BackupJob but got a %T", old))
}
// Enforce immutability of backupClassName
if oldJob.Spec.BackupClassName != j.Spec.BackupClassName {
return nil, fmt.Errorf("backupClassName is immutable and cannot be changed from %q to %q", oldJob.Spec.BackupClassName, j.Spec.BackupClassName)
}
return nil, nil
}
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (j *BackupJob) ValidateDelete() (admission.Warnings, error) {
// No validation needed for deletion
return nil, nil
}

View file

@ -1,334 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
func TestBackupJob_ValidateCreate(t *testing.T) {
tests := []struct {
name string
job *BackupJob
wantErr bool
errMsg string
}{
{
name: "valid BackupJob with backupClassName",
job: &BackupJob{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
Namespace: "default",
},
Spec: BackupJobSpec{
ApplicationRef: corev1.TypedLocalObjectReference{
Kind: "VirtualMachine",
Name: "vm1",
},
BackupClassName: "velero",
},
},
wantErr: false,
},
{
name: "BackupJob with empty backupClassName should be rejected",
job: &BackupJob{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
Namespace: "default",
},
Spec: BackupJobSpec{
ApplicationRef: corev1.TypedLocalObjectReference{
Kind: "VirtualMachine",
Name: "vm1",
},
BackupClassName: "",
},
},
wantErr: true,
errMsg: "backupClassName is required and cannot be empty",
},
{
name: "BackupJob with whitespace-only backupClassName should be rejected",
job: &BackupJob{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
Namespace: "default",
},
Spec: BackupJobSpec{
ApplicationRef: corev1.TypedLocalObjectReference{
Kind: "VirtualMachine",
Name: "vm1",
},
BackupClassName: " ",
},
},
wantErr: true,
errMsg: "backupClassName is required and cannot be empty",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
warnings, err := tt.job.ValidateCreate()
if (err != nil) != tt.wantErr {
t.Errorf("ValidateCreate() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantErr && err != nil {
if tt.errMsg != "" && err.Error() != tt.errMsg {
t.Errorf("ValidateCreate() error message = %v, want %v", err.Error(), tt.errMsg)
}
}
if warnings != nil && len(warnings) > 0 {
t.Logf("ValidateCreate() warnings = %v", warnings)
}
})
}
}
func TestBackupJob_ValidateUpdate(t *testing.T) {
baseJob := &BackupJob{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
Namespace: "default",
},
Spec: BackupJobSpec{
ApplicationRef: corev1.TypedLocalObjectReference{
Kind: "VirtualMachine",
Name: "vm1",
},
BackupClassName: "velero",
},
}
tests := []struct {
name string
old runtime.Object
new *BackupJob
wantErr bool
errMsg string
}{
{
name: "update with same backupClassName should succeed",
old: baseJob,
new: &BackupJob{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
Namespace: "default",
},
Spec: BackupJobSpec{
ApplicationRef: corev1.TypedLocalObjectReference{
Kind: "VirtualMachine",
Name: "vm1",
},
BackupClassName: "velero", // Same as old
},
},
wantErr: false,
},
{
name: "update changing backupClassName should be rejected",
old: baseJob,
new: &BackupJob{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
Namespace: "default",
},
Spec: BackupJobSpec{
ApplicationRef: corev1.TypedLocalObjectReference{
Kind: "VirtualMachine",
Name: "vm1",
},
BackupClassName: "different-class", // Changed!
},
},
wantErr: true,
errMsg: "backupClassName is immutable and cannot be changed from \"velero\" to \"different-class\"",
},
{
name: "update changing other fields but keeping backupClassName should succeed",
old: baseJob,
new: &BackupJob{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
Namespace: "default",
Labels: map[string]string{
"new-label": "value",
},
},
Spec: BackupJobSpec{
ApplicationRef: corev1.TypedLocalObjectReference{
Kind: "VirtualMachine",
Name: "vm2", // Changed application
},
BackupClassName: "velero", // Same as old
},
},
wantErr: false,
},
{
name: "update when old backupClassName is empty should be rejected",
old: &BackupJob{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
Namespace: "default",
},
Spec: BackupJobSpec{
ApplicationRef: corev1.TypedLocalObjectReference{
Kind: "VirtualMachine",
Name: "vm1",
},
BackupClassName: "", // Empty in old
},
},
new: &BackupJob{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
Namespace: "default",
},
Spec: BackupJobSpec{
ApplicationRef: corev1.TypedLocalObjectReference{
Kind: "VirtualMachine",
Name: "vm1",
},
BackupClassName: "velero", // Setting it for the first time
},
},
wantErr: true,
errMsg: "backupClassName is immutable",
},
{
name: "update changing from non-empty to different non-empty should be rejected",
old: &BackupJob{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
Namespace: "default",
},
Spec: BackupJobSpec{
ApplicationRef: corev1.TypedLocalObjectReference{
Kind: "VirtualMachine",
Name: "vm1",
},
BackupClassName: "class-a",
},
},
new: &BackupJob{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
Namespace: "default",
},
Spec: BackupJobSpec{
ApplicationRef: corev1.TypedLocalObjectReference{
Kind: "VirtualMachine",
Name: "vm1",
},
BackupClassName: "class-b", // Changed from class-a
},
},
wantErr: true,
errMsg: "backupClassName is immutable and cannot be changed from \"class-a\" to \"class-b\"",
},
{
name: "update with invalid old object type should be rejected",
old: &corev1.Pod{ // Wrong type - will be cast to runtime.Object in test
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
Namespace: "default",
},
},
new: baseJob,
wantErr: true,
errMsg: "expected a BackupJob but got a",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
warnings, err := tt.new.ValidateUpdate(tt.old)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateUpdate() error = %v, wantErr %v", err, tt.wantErr)
if err != nil {
t.Logf("Error message: %v", err.Error())
}
return
}
if tt.wantErr && err != nil {
if tt.errMsg != "" {
if tt.errMsg != "" && !contains(err.Error(), tt.errMsg) {
t.Errorf("ValidateUpdate() error message = %v, want contains %v", err.Error(), tt.errMsg)
}
}
}
if warnings != nil && len(warnings) > 0 {
t.Logf("ValidateUpdate() warnings = %v", warnings)
}
})
}
}
func TestBackupJob_ValidateDelete(t *testing.T) {
job := &BackupJob{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
Namespace: "default",
},
Spec: BackupJobSpec{
ApplicationRef: corev1.TypedLocalObjectReference{
Kind: "VirtualMachine",
Name: "vm1",
},
BackupClassName: "velero",
},
}
warnings, err := job.ValidateDelete()
if err != nil {
t.Errorf("ValidateDelete() should never return an error, got %v", err)
}
if warnings != nil && len(warnings) > 0 {
t.Logf("ValidateDelete() warnings = %v", warnings)
}
}
func TestBackupJob_Default(t *testing.T) {
job := &BackupJob{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
Namespace: "default",
},
Spec: BackupJobSpec{
ApplicationRef: corev1.TypedLocalObjectReference{
Kind: "VirtualMachine",
Name: "vm1",
},
BackupClassName: "velero",
},
}
// Default() should not panic and should not modify the object
originalClassName := job.Spec.BackupClassName
job.Default()
if job.Spec.BackupClassName != originalClassName {
t.Errorf("Default() should not modify backupClassName, got %v, want %v", job.Spec.BackupClassName, originalClassName)
}
}
// Helper function to check if a string contains a substring
func contains(s, substr string) bool {
if len(substr) == 0 {
return true
}
if len(s) < len(substr) {
return false
}
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}

View file

@ -162,21 +162,6 @@ func main() {
os.Exit(1)
}
if err = (&backupcontroller.BackupJobReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Recorder: mgr.GetEventRecorderFor("backup-controller"),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "BackupJob")
os.Exit(1)
}
// Register BackupJob webhook for validation (immutability of backupClassName)
if err = backupsv1alpha1.SetupBackupJobWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "BackupJob")
os.Exit(1)
}
// +kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {

View file

@ -19,7 +19,7 @@ set -o nounset
set -o pipefail
SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)}
CODEGEN_PKG=${CODEGEN_PKG:-~/go/pkg/mod/k8s.io/code-generator@v0.34.1}
API_KNOWN_VIOLATIONS_DIR="${API_KNOWN_VIOLATIONS_DIR:-"${SCRIPT_ROOT}/api/api-rules"}"
UPDATE_API_KNOWN_VIOLATIONS="${UPDATE_API_KNOWN_VIOLATIONS:-true}"
CONTROLLER_GEN="go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.16.4"

View file

@ -2,7 +2,6 @@ package backupcontroller
import (
"context"
"fmt"
"net/http"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@ -22,8 +21,8 @@ import (
backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1"
)
// BackupVeleroStrategyReconciler reconciles BackupJob with a strategy referencing
// Velero.strategy.backups.cozystack.io objects.
// BackupJobReconciler reconciles BackupJob with a strategy from the
// strategy.backups.cozystack.io API group.
type BackupJobReconciler struct {
client.Client
dynamic.Interface

View file

@ -106,9 +106,10 @@ func (r *RestoreJobReconciler) SetupWithManager(mgr ctrl.Manager) error {
// getTargetApplicationRef determines the effective target application reference.
// According to DESIGN.md, if spec.targetApplicationRef is omitted, drivers SHOULD
// restore into backup.spec.applicationRef.
// The returned reference is normalized to ensure APIGroup has a default value.
func (r *RestoreJobReconciler) getTargetApplicationRef(restoreJob *backupsv1alpha1.RestoreJob, backup *backupsv1alpha1.Backup) corev1.TypedLocalObjectReference {
if restoreJob.Spec.TargetApplicationRef != nil {
return *restoreJob.Spec.TargetApplicationRef
return backupsv1alpha1.NormalizeApplicationRef(*restoreJob.Spec.TargetApplicationRef)
}
return backup.Spec.ApplicationRef
}

View file

@ -208,30 +208,6 @@ func (r *BackupJobReconciler) reconcileVelero(ctx context.Context, j *backupsv1a
return ctrl.Result{RequeueAfter: 5 * time.Second}, nil
}
func (r *BackupJobReconciler) markBackupJobFailed(ctx context.Context, backupJob *backupsv1alpha1.BackupJob, message string) (ctrl.Result, error) {
logger := getLogger(ctx)
now := metav1.Now()
backupJob.Status.CompletedAt = &now
backupJob.Status.Phase = backupsv1alpha1.BackupJobPhaseFailed
backupJob.Status.Message = message
// Add condition
backupJob.Status.Conditions = append(backupJob.Status.Conditions, metav1.Condition{
Type: "Ready",
Status: metav1.ConditionFalse,
Reason: "BackupFailed",
Message: message,
LastTransitionTime: now,
})
if err := r.Status().Update(ctx, backupJob); err != nil {
logger.Error(err, "failed to update BackupJob status to Failed")
return ctrl.Result{}, err
}
logger.Debug("BackupJob failed", "message", message)
return ctrl.Result{}, nil
}
func (r *BackupJobReconciler) createVeleroBackup(ctx context.Context, backupJob *backupsv1alpha1.BackupJob, strategy *strategyv1alpha1.Velero, resolved *ResolvedBackupConfig) error {
logger := getLogger(ctx)
logger.Debug("createVeleroBackup called", "strategy", strategy.Name)
@ -476,39 +452,48 @@ func (r *RestoreJobReconciler) createVeleroRestore(ctx context.Context, restoreJ
return fmt.Errorf("failed to get target application: %w", err)
}
// Template the restore spec from the strategy
veleroRestoreSpec, err := template.Template(&strategy.Spec.Template.RestoreSpec, app.Object)
if err != nil {
return fmt.Errorf("failed to template Velero Restore spec: %w", err)
// Build template context
templateContext := map[string]interface{}{
"Application": app.Object,
// TODO: Parameters are not currently stored on Backup, so they're unavailable during restore.
// This is a design limitation that should be addressed by persisting Parameters on the Backup object.
"Parameters": map[string]string{},
}
// Template the restore spec from the strategy, or use defaults if not specified
var veleroRestoreSpec velerov1.RestoreSpec
if strategy.Spec.Template.RestoreSpec != nil {
templatedSpec, err := template.Template(strategy.Spec.Template.RestoreSpec, templateContext)
if err != nil {
return fmt.Errorf("failed to template Velero Restore spec: %w", err)
}
veleroRestoreSpec = *templatedSpec
}
// Set the backupName in the spec (required by Velero)
veleroRestoreSpec.BackupName = veleroBackupName
generateName := fmt.Sprintf("%s.%s-", restoreJob.Namespace, restoreJob.Name)
veleroRestore := &velerov1.Restore{
ObjectMeta: metav1.ObjectMeta{
GenerateName: fmt.Sprintf("%s.%s-", restoreJob.Namespace, restoreJob.Name),
GenerateName: generateName,
Namespace: veleroNamespace,
Labels: map[string]string{
backupsv1alpha1.OwningJobNameLabel: restoreJob.Name,
backupsv1alpha1.OwningJobNamespaceLabel: restoreJob.Namespace,
},
},
Spec: *veleroRestoreSpec,
Spec: veleroRestoreSpec,
}
name := veleroRestore.GenerateName
if err := r.Create(ctx, veleroRestore); err != nil {
if veleroRestore.Name != "" {
name = veleroRestore.Name
}
logger.Error(err, "failed to create Velero Restore", "name", veleroRestore.Name)
logger.Error(err, "failed to create Velero Restore", "generateName", generateName)
r.Recorder.Event(restoreJob, corev1.EventTypeWarning, "VeleroRestoreCreationFailed",
fmt.Sprintf("Failed to create Velero Restore %s/%s: %v", veleroNamespace, name, err))
fmt.Sprintf("Failed to create Velero Restore %s/%s: %v", veleroNamespace, generateName, err))
return err
}
logger.Debug("created Velero Restore", "name", veleroRestore.Name, "namespace", veleroRestore.Namespace)
r.Recorder.Event(restoreJob, corev1.EventTypeNormal, "VeleroRestoreCreated",
fmt.Sprintf("Created Velero Restore %s/%s", veleroNamespace, name))
fmt.Sprintf("Created Velero Restore %s/%s", veleroNamespace, veleroRestore.Name))
return nil
}

View file

@ -74,7 +74,12 @@ spec:
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.
This field is immutable once the BackupJob is created.
minLength: 1
type: string
x-kubernetes-validations:
- message: backupClassName is immutable
rule: self == oldSelf
planRef:
description: |-
PlanRef refers to the Plan that requested this backup run.

View file

@ -14,7 +14,11 @@ spec:
singular: restorejob
scope: Namespaced
versions:
- name: v1alpha1
- additionalPrinterColumns:
- jsonPath: .status.phase
name: Phase
type: string
name: v1alpha1
schema:
openAPIV3Schema:
description: RestoreJob represents a single execution of a restore from a
@ -166,3 +170,5 @@ spec:
type: object
served: true
storage: true
subresources:
status: {}

View file

@ -1,14 +0,0 @@
kind: BackupClass
metadata:
name: velero
spec:
strategies:
- strategyRef:
apiGroup: strategy.backups.cozystack.io
kind: Velero
name: velero-backup-strategy-for-virtualmachines
application:
apiVersion: apps.cozystack.io
kind: VirtualMachine
parameters:
backupStorageLocationName: default

View file

@ -1,10 +0,0 @@
apiVersion: strategy.backups.cozystack.io/v1alpha1
kind: Velero
metadata:
name: velero-strategy-default
spec:
template:
spec:
ttl: 720h
includedNamespaces:
- "*"

View file

@ -45,6 +45,428 @@ spec:
VeleroTemplate describes the data a backup.velero.io should have when
templated from a Velero backup strategy.
properties:
restoreSpec:
description: RestoreSpec defines the specification for a Velero
restore.
properties:
backupName:
description: |-
BackupName is the unique name of the Velero backup to restore
from.
type: string
excludedNamespaces:
description: |-
ExcludedNamespaces contains a list of namespaces that are not
included in the restore.
items:
type: string
nullable: true
type: array
excludedResources:
description: |-
ExcludedResources is a slice of resource names that are not
included in the restore.
items:
type: string
nullable: true
type: array
existingResourcePolicy:
description: ExistingResourcePolicy specifies the restore
behavior for the Kubernetes resource to be restored
nullable: true
type: string
hooks:
description: Hooks represent custom behaviors that should
be executed during or post restore.
properties:
resources:
items:
description: |-
RestoreResourceHookSpec defines one or more RestoreResrouceHooks that should be executed based on
the rules defined for namespaces, resources, and label selector.
properties:
excludedNamespaces:
description: ExcludedNamespaces specifies the namespaces
to which this hook spec does not apply.
items:
type: string
nullable: true
type: array
excludedResources:
description: ExcludedResources specifies the resources
to which this hook spec does not apply.
items:
type: string
nullable: true
type: array
includedNamespaces:
description: |-
IncludedNamespaces specifies the namespaces to which this hook spec applies. If empty, it applies
to all namespaces.
items:
type: string
nullable: true
type: array
includedResources:
description: |-
IncludedResources specifies the resources to which this hook spec applies. If empty, it applies
to all resources.
items:
type: string
nullable: true
type: array
labelSelector:
description: LabelSelector, if specified, filters
the resources to which this hook spec applies.
nullable: true
properties:
matchExpressions:
description: matchExpressions is a list of label
selector requirements. The requirements are
ANDed.
items:
description: |-
A label selector requirement is a selector that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that
the selector applies to.
type: string
operator:
description: |-
operator represents a key's relationship to a set of values.
Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: |-
values is an array of string values. If the operator is In or NotIn,
the values array must be non-empty. If the operator is Exists or DoesNotExist,
the values array must be empty. This array is replaced during a strategic
merge patch.
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
description: |-
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions, whose key field is "key", the
operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
name:
description: Name is the name of this hook.
type: string
postHooks:
description: PostHooks is a list of RestoreResourceHooks
to execute during and after restoring a resource.
items:
description: RestoreResourceHook defines a restore
hook for a resource.
properties:
exec:
description: Exec defines an exec restore
hook.
properties:
command:
description: Command is the command and
arguments to execute from within a container
after a pod has been restored.
items:
type: string
minItems: 1
type: array
container:
description: |-
Container is the container in the pod where the command should be executed. If not specified,
the pod's first container is used.
type: string
execTimeout:
description: |-
ExecTimeout defines the maximum amount of time Velero should wait for the hook to complete before
considering the execution a failure.
type: string
onError:
description: OnError specifies how Velero
should behave if it encounters an error
executing this hook.
enum:
- Continue
- Fail
type: string
waitForReady:
description: WaitForReady ensures command
will be launched when container is Ready
instead of Running.
nullable: true
type: boolean
waitTimeout:
description: |-
WaitTimeout defines the maximum amount of time Velero should wait for the container to be Ready
before attempting to run the command.
type: string
required:
- command
type: object
init:
description: Init defines an init restore
hook.
properties:
initContainers:
description: InitContainers is list of
init containers to be added to a pod
during its restore.
items:
type: object
x-kubernetes-preserve-unknown-fields: true
type: array
x-kubernetes-preserve-unknown-fields: true
timeout:
description: Timeout defines the maximum
amount of time Velero should wait for
the initContainers to complete.
type: string
type: object
type: object
type: array
required:
- name
type: object
type: array
type: object
includeClusterResources:
description: |-
IncludeClusterResources specifies whether cluster-scoped resources
should be included for consideration in the restore. If null, defaults
to true.
nullable: true
type: boolean
includedNamespaces:
description: |-
IncludedNamespaces is a slice of namespace names to include objects
from. If empty, all namespaces are included.
items:
type: string
nullable: true
type: array
includedResources:
description: |-
IncludedResources is a slice of resource names to include
in the restore. If empty, all resources in the backup are included.
items:
type: string
nullable: true
type: array
itemOperationTimeout:
description: |-
ItemOperationTimeout specifies the time used to wait for RestoreItemAction operations
The default value is 4 hour.
type: string
labelSelector:
description: |-
LabelSelector is a metav1.LabelSelector to filter with
when restoring individual objects from the backup. If empty
or nil, all objects are included. Optional.
nullable: true
properties:
matchExpressions:
description: matchExpressions is a list of label selector
requirements. The requirements are ANDed.
items:
description: |-
A label selector requirement is a selector that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector
applies to.
type: string
operator:
description: |-
operator represents a key's relationship to a set of values.
Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: |-
values is an array of string values. If the operator is In or NotIn,
the values array must be non-empty. If the operator is Exists or DoesNotExist,
the values array must be empty. This array is replaced during a strategic
merge patch.
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
description: |-
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions, whose key field is "key", the
operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
namespaceMapping:
additionalProperties:
type: string
description: |-
NamespaceMapping is a map of source namespace names
to target namespace names to restore into. Any source
namespaces not included in the map will be restored into
namespaces of the same name.
type: object
orLabelSelectors:
description: |-
OrLabelSelectors is list of metav1.LabelSelector to filter with
when restoring individual objects from the backup. If multiple provided
they will be joined by the OR operator. LabelSelector as well as
OrLabelSelectors cannot co-exist in restore request, only one of them
can be used
items:
description: |-
A label selector is a label query over a set of resources. The result of matchLabels and
matchExpressions are ANDed. An empty label selector matches all objects. A null
label selector matches no objects.
properties:
matchExpressions:
description: matchExpressions is a list of label selector
requirements. The requirements are ANDed.
items:
description: |-
A label selector requirement is a selector that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector
applies to.
type: string
operator:
description: |-
operator represents a key's relationship to a set of values.
Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: |-
values is an array of string values. If the operator is In or NotIn,
the values array must be non-empty. If the operator is Exists or DoesNotExist,
the values array must be empty. This array is replaced during a strategic
merge patch.
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
description: |-
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions, whose key field is "key", the
operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
nullable: true
type: array
preserveNodePorts:
description: PreserveNodePorts specifies whether to restore
old nodePorts from backup.
nullable: true
type: boolean
resourceModifier:
description: ResourceModifier specifies the reference to JSON
resource patches that should be applied to resources before
restoration.
nullable: true
properties:
apiGroup:
description: |-
APIGroup is the group for the resource being referenced.
If APIGroup is not specified, the specified Kind must be in the core API group.
For any other third-party types, APIGroup is required.
type: string
kind:
description: Kind is the type of resource being referenced
type: string
name:
description: Name is the name of resource being referenced
type: string
required:
- kind
- name
type: object
x-kubernetes-map-type: atomic
restorePVs:
description: |-
RestorePVs specifies whether to restore all included
PVs from snapshot
nullable: true
type: boolean
restoreStatus:
description: |-
RestoreStatus specifies which resources we should restore the status
field. If nil, no objects are included. Optional.
nullable: true
properties:
excludedResources:
description: ExcludedResources specifies the resources
to which will not restore the status.
items:
type: string
nullable: true
type: array
includedResources:
description: |-
IncludedResources specifies the resources to which will restore the status.
If empty, it applies to all resources.
items:
type: string
nullable: true
type: array
type: object
scheduleName:
description: |-
ScheduleName is the unique name of the Velero schedule to restore
from. If specified, and BackupName is empty, Velero will restore
from the most recent successful backup created from this schedule.
type: string
uploaderConfig:
description: UploaderConfig specifies the configuration for
the restore.
nullable: true
properties:
parallelFilesDownload:
description: ParallelFilesDownload is the concurrency
number setting for restore.
type: integer
writeSparseFiles:
description: WriteSparseFiles is a flag to indicate whether
write files sparsely or not.
nullable: true
type: boolean
type: object
type: object
spec:
description: BackupSpec defines the specification for a Velero
backup.

View file

@ -3,13 +3,13 @@ kind: Velero
metadata:
name: velero-backup-strategy-for-virtualmachines
spec:
backupTemplate:
template:
spec: # see https://velero.io/docs/v1.9/api-types/backup/
includedNamespaces:
- {{ .metadata.namespace }}
- {{ .Application.metadata.namespace }}
labelSelector:
apps.cozystack.io/application.Kind: {{ .kind }}
apps.cozystack.io/application.Kind: {{ .Application.kind }}
includedResources:
- helmreleases.helm.toolkit.fluxcd.io

View file

@ -258,6 +258,13 @@ func schema_pkg_apis_apps_v1alpha1_ApplicationStatus(ref common.ReferenceCallbac
Format: "",
},
},
"externalIPsCount": {
SchemaProps: spec.SchemaProps{
Description: "ExternalIPsCount holds the number of LoadBalancer services with assigned external IPs for Tenant applications.",
Type: []string{"integer"},
Format: "int32",
},
},
},
},
},