121 lines
4.0 KiB
Go
121 lines
4.0 KiB
Go
package k8s
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
)
|
|
|
|
const longhornSystemNamespace = "longhorn-system"
|
|
|
|
var longhornBackupGVR = schema.GroupVersionResource{
|
|
Group: "longhorn.io",
|
|
Version: "v1beta2",
|
|
Resource: "backups",
|
|
}
|
|
|
|
// LonghornBackupSummary is the subset of the Longhorn Backup CRD that Soteria
|
|
// needs while waiting for an async backup to reach a terminal state.
|
|
type LonghornBackupSummary struct {
|
|
Name string
|
|
SnapshotName string
|
|
Namespace string
|
|
PVC string
|
|
State string
|
|
Error string
|
|
Progress int64
|
|
CreatedAt string
|
|
}
|
|
|
|
// ListLonghornBackups returns Soteria-owned Longhorn Backup CRDs.
|
|
func (c *Client) ListLonghornBackups(ctx context.Context) ([]LonghornBackupSummary, error) {
|
|
if c.Dynamic == nil {
|
|
return nil, fmt.Errorf("dynamic Kubernetes client is unavailable")
|
|
}
|
|
|
|
list, err := c.Dynamic.Resource(longhornBackupGVR).Namespace(longhornSystemNamespace).List(ctx, metav1.ListOptions{})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list Longhorn backups: %w", err)
|
|
}
|
|
|
|
items := make([]LonghornBackupSummary, 0, len(list.Items))
|
|
for _, item := range list.Items {
|
|
summary := summarizeLonghornBackup(item)
|
|
if summary.Namespace != "" && summary.PVC != "" {
|
|
items = append(items, summary)
|
|
}
|
|
}
|
|
sortLonghornBackupsNewestFirst(items)
|
|
return items, nil
|
|
}
|
|
|
|
// GetLonghornBackupBySnapshot returns the newest Longhorn Backup CRD for a snapshot.
|
|
func (c *Client) GetLonghornBackupBySnapshot(ctx context.Context, snapshotName string) (LonghornBackupSummary, bool, error) {
|
|
snapshotName = strings.TrimSpace(snapshotName)
|
|
if snapshotName == "" {
|
|
return LonghornBackupSummary{}, false, fmt.Errorf("snapshot name is required")
|
|
}
|
|
if c.Dynamic == nil {
|
|
return LonghornBackupSummary{}, false, fmt.Errorf("dynamic Kubernetes client is unavailable")
|
|
}
|
|
|
|
list, err := c.Dynamic.Resource(longhornBackupGVR).Namespace(longhornSystemNamespace).List(ctx, metav1.ListOptions{})
|
|
if err != nil {
|
|
return LonghornBackupSummary{}, false, fmt.Errorf("list Longhorn backups: %w", err)
|
|
}
|
|
|
|
matches := make([]LonghornBackupSummary, 0, len(list.Items))
|
|
for _, item := range list.Items {
|
|
summary := summarizeLonghornBackup(item)
|
|
if summary.SnapshotName == snapshotName {
|
|
matches = append(matches, summary)
|
|
}
|
|
}
|
|
if len(matches) == 0 {
|
|
return LonghornBackupSummary{}, false, nil
|
|
}
|
|
|
|
sortLonghornBackupsNewestFirst(matches)
|
|
return matches[0], true, nil
|
|
}
|
|
|
|
func summarizeLonghornBackup(item unstructured.Unstructured) LonghornBackupSummary {
|
|
snapshotName, _, _ := unstructured.NestedString(item.Object, "spec", "snapshotName")
|
|
state, _, _ := unstructured.NestedString(item.Object, "status", "state")
|
|
errorMessage, _, _ := unstructured.NestedString(item.Object, "status", "error")
|
|
progress, _, _ := unstructured.NestedInt64(item.Object, "status", "progress")
|
|
specLabels, _, _ := unstructured.NestedStringMap(item.Object, "spec", "labels")
|
|
createdAt, _, _ := unstructured.NestedString(item.Object, "status", "backupCreatedAt")
|
|
if strings.TrimSpace(createdAt) == "" {
|
|
createdAt, _, _ = unstructured.NestedString(item.Object, "status", "snapshotCreatedAt")
|
|
}
|
|
if strings.TrimSpace(createdAt) == "" {
|
|
createdAt = item.GetCreationTimestamp().UTC().Format("2006-01-02T15:04:05Z")
|
|
}
|
|
|
|
return LonghornBackupSummary{
|
|
Name: item.GetName(),
|
|
SnapshotName: strings.TrimSpace(snapshotName),
|
|
Namespace: strings.TrimSpace(specLabels["soteria.bstein.dev/namespace"]),
|
|
PVC: strings.TrimSpace(specLabels["soteria.bstein.dev/pvc"]),
|
|
State: strings.TrimSpace(state),
|
|
Error: strings.TrimSpace(errorMessage),
|
|
Progress: progress,
|
|
CreatedAt: strings.TrimSpace(createdAt),
|
|
}
|
|
}
|
|
|
|
func sortLonghornBackupsNewestFirst(items []LonghornBackupSummary) {
|
|
sort.Slice(items, func(i, j int) bool {
|
|
if items[i].CreatedAt == items[j].CreatedAt {
|
|
return items[i].Name > items[j].Name
|
|
}
|
|
return items[i].CreatedAt > items[j].CreatedAt
|
|
})
|
|
}
|