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 State string Error string Progress int64 CreatedAt string } // 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 } sort.Slice(matches, func(i, j int) bool { if matches[i].CreatedAt == matches[j].CreatedAt { return matches[i].Name > matches[j].Name } return matches[i].CreatedAt > matches[j].CreatedAt }) 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") 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), State: strings.TrimSpace(state), Error: strings.TrimSpace(errorMessage), Progress: progress, CreatedAt: strings.TrimSpace(createdAt), } }