Backup rework pt 2 (#1967)

<!-- Thank you for making a contribution! Here are some tips for you:
- Start the PR title with the [label] of Cozystack component:
- For system components: [platform], [system], [linstor], [cilium],
[kube-ovn], [dashboard], [cluster-api], etc.
- For managed apps: [apps], [tenant], [kubernetes], [postgres],
[virtual-machine] etc.
- For development and maintenance: [tests], [ci], [docs], [maintenance].
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `backport` if it's a bugfix that needs to be backported
to a previous version.
-->

## What this PR does


### Release note

<!--  Write a release note:
- Explain what has changed internally and for users.
- Start with the same [label] as in the PR title
- Follow the guidelines at
https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md.
-->

```release-note
[
- add BackupClass Velero and strategy for VM
- dashboard fixes for BackupClass and simplifed Plan/BackupJob API
]
```
This commit is contained in:
Timofei Larkin 2026-02-03 21:27:38 +04:00 committed by GitHub
commit 3eaadfc95c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 90 additions and 21 deletions

View file

@ -495,6 +495,27 @@ func CreateAllCustomFormsOverrides() []*dashboardv1alpha1.CustomFormsOverride {
createFormItem("spec.ports", "Ports", "array"),
},
}),
// Plans form override - backups.cozystack.io/v1alpha1
createCustomFormsOverride("default-/backups.cozystack.io/v1alpha1/plans", map[string]any{
"formItems": []any{
createFormItem("metadata.name", "Name", "text"),
createFormItem("metadata.namespace", "Namespace", "text"),
createFormItem("spec.applicationRef.kind", "Application Kind", "text"),
createFormItem("spec.applicationRef.name", "Application Name", "text"),
createFormItemWithAPI("spec.backupClassName", "Backup Class", "select", map[string]any{
"api": map[string]any{
"fetchUrl": "/api/clusters/{clusterName}/k8s/apis/backups.cozystack.io/v1alpha1/backupclasses",
"pathToItems": []any{"items"},
"pathToValue": []any{"metadata", "name"},
"pathToLabel": []any{"metadata", "name"},
"clusterNameVar": "clusterName",
},
}),
createFormItem("spec.schedule.type", "Schedule Type", "text"),
createFormItem("spec.schedule.cron", "Schedule Cron", "text"),
},
}),
}
}
@ -1556,13 +1577,9 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
antdText("application-ref-label", true, "Application", nil),
parsedText("application-ref-value", "{reqsJsonPath[0]['.spec.applicationRef.kind']['-']}.{reqsJsonPath[0]['.spec.applicationRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.applicationRef.name']['-']}", nil),
}),
antdFlexVertical("spec-storage-ref-block", 4, []any{
antdText("storage-ref-label", true, "Storage", nil),
parsedText("storage-ref-value", "{reqsJsonPath[0]['.spec.storageRef.kind']['-']}.{reqsJsonPath[0]['.spec.storageRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.storageRef.name']['-']}", nil),
}),
antdFlexVertical("spec-strategy-ref-block", 4, []any{
antdText("strategy-ref-label", true, "Strategy", nil),
parsedText("strategy-ref-value", "{reqsJsonPath[0]['.spec.strategyRef.kind']['-']}.{reqsJsonPath[0]['.spec.strategyRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.strategyRef.name']['-']}", nil),
antdFlexVertical("spec-backup-class-name-block", 4, []any{
antdText("backup-class-name-label", true, "Backup Class", nil),
parsedText("backup-class-name-value", "{reqsJsonPath[0]['.spec.backupClassName']['-']}", nil),
}),
antdFlexVertical("spec-schedule-type-block", 4, []any{
antdText("schedule-type-label", true, "Schedule Type", nil),
@ -1680,13 +1697,9 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
antdText("application-ref-label", true, "Application", nil),
parsedText("application-ref-value", "{reqsJsonPath[0]['.spec.applicationRef.kind']['-']}.{reqsJsonPath[0]['.spec.applicationRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.applicationRef.name']['-']}", nil),
}),
antdFlexVertical("spec-storage-ref-block", 4, []any{
antdText("storage-ref-label", true, "Storage", nil),
parsedText("storage-ref-value", "{reqsJsonPath[0]['.spec.storageRef.kind']['-']}.{reqsJsonPath[0]['.spec.storageRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.storageRef.name']['-']}", nil),
}),
antdFlexVertical("spec-strategy-ref-block", 4, []any{
antdText("strategy-ref-label", true, "Strategy", nil),
parsedText("strategy-ref-value", "{reqsJsonPath[0]['.spec.strategyRef.name']['-']}", nil),
antdFlexVertical("spec-backup-class-name-block", 4, []any{
antdText("backup-class-name-label", true, "Backup Class", nil),
parsedText("backup-class-name-value", "{reqsJsonPath[0]['.spec.backupClassName']['-']}", nil),
}),
antdFlexVertical("status-backup-ref-block", 4, []any{
antdText("backup-ref-label", true, "Backup Ref", nil),
@ -1864,13 +1877,9 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
antdText("application-ref-label", true, "Application", nil),
parsedText("application-ref-value", "{reqsJsonPath[0]['.spec.applicationRef.kind']['-']}.{reqsJsonPath[0]['.spec.applicationRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.applicationRef.name']['-']}", nil),
}),
antdFlexVertical("spec-storage-ref-block", 4, []any{
antdText("storage-ref-label", true, "Storage", nil),
parsedText("storage-ref-value", "{reqsJsonPath[0]['.spec.storageRef.kind']['-']}.{reqsJsonPath[0]['.spec.storageRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.storageRef.name']['-']}", nil),
}),
antdFlexVertical("spec-strategy-ref-block", 4, []any{
antdText("strategy-ref-label", true, "Strategy", nil),
parsedText("strategy-ref-value", "{reqsJsonPath[0]['.spec.strategyRef.kind']['-']}.{reqsJsonPath[0]['.spec.strategyRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.strategyRef.name']['-']}", nil),
antdFlexVertical("spec-backup-class-name-block", 4, []any{
antdText("backup-class-name-label", true, "Backup Class", nil),
parsedText("backup-class-name-value", "{reqsJsonPath[0]['.spec.backupClassName']['-']}", nil),
}),
antdFlexVertical("status-artifact-uri-block", 4, []any{
antdText("artifact-uri-label", true, "Artifact URI", nil),
@ -2070,6 +2079,20 @@ func createFormItem(path, label, fieldType string) map[string]any {
}
}
// createFormItemWithAPI creates a form item with API endpoint for resource-based selects
func createFormItemWithAPI(path, label, fieldType string, apiConfig map[string]any) map[string]any {
item := map[string]any{
"path": path,
"label": label,
"type": fieldType,
}
// Merge API configuration into the form item
for key, value := range apiConfig {
item[key] = value
}
return item
}
// ---------------- Workloadmonitor specific functions ----------------
// createNamespaceHeader creates a header specifically for namespace with correct colors and text

View file

@ -0,0 +1,14 @@
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

@ -0,0 +1,32 @@
apiVersion: strategy.backups.cozystack.io/v1alpha1
kind: Velero
metadata:
name: velero-backup-strategy-for-virtualmachines
spec:
backupTemplate:
spec: # see https://velero.io/docs/v1.9/api-types/backup/
includedNamespaces:
- {{ .metadata.namespace }}
labelSelector:
apps.cozystack.io/application.Kind: {{ .kind }}
includedResources:
- helmreleases.helm.toolkit.fluxcd.io
- virtualmachines.kubevirt.io
- virtualmachineinstances.kubevirt.io
- datavolumes.cdi.kubevirt.io
- persistentvolumeclaims
- services
- configmaps
- secrets
storageLocation: {{ .Parameters.backupStorageLocationName }}
volumeSnapshotLocations:
- {{ .Parameters.backupStorageLocationName }}
snapshotVolumes: true
snapshotMoveData: true
ttl: 720h0m0s
itemOperationTimeout: 24h0m0s