package k8s import ( "context" "fmt" "sort" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // PVCSummary describes a bound PVC and the volume metadata Soteria needs. type PVCSummary struct { Namespace string Name string VolumeName string Phase string StorageClass string Capacity string AccessModes []string } // ResolvePVCVolume loads a PVC and its bound PV so backup handlers can act on the real volume. func (c *Client) ResolvePVCVolume(ctx context.Context, namespace, pvcName string) (string, *corev1.PersistentVolumeClaim, *corev1.PersistentVolume, error) { pvc, err := c.Clientset.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, pvcName, metav1.GetOptions{}) if err != nil { return "", nil, nil, fmt.Errorf("get pvc %s/%s: %w", namespace, pvcName, err) } if pvc.Spec.VolumeName == "" { return "", pvc, nil, fmt.Errorf("pvc %s/%s has no bound volume", namespace, pvcName) } pv, err := c.Clientset.CoreV1().PersistentVolumes().Get(ctx, pvc.Spec.VolumeName, metav1.GetOptions{}) if err != nil { return "", pvc, nil, fmt.Errorf("get pv %s: %w", pvc.Spec.VolumeName, err) } return pvc.Spec.VolumeName, pvc, pv, nil } // ListBoundPVCs returns all bound PVCs in a stable namespace/name order. func (c *Client) ListBoundPVCs(ctx context.Context) ([]PVCSummary, error) { list, err := c.Clientset.CoreV1().PersistentVolumeClaims("").List(ctx, metav1.ListOptions{}) if err != nil { return nil, fmt.Errorf("list pvcs: %w", err) } items := make([]PVCSummary, 0, len(list.Items)) for _, pvc := range list.Items { if pvc.Spec.VolumeName == "" || pvc.Status.Phase != corev1.ClaimBound { continue } storageClass := "" if pvc.Spec.StorageClassName != nil { storageClass = *pvc.Spec.StorageClassName } capacity := "" if quantity, ok := pvc.Status.Capacity[corev1.ResourceStorage]; ok { capacity = quantity.String() } else if quantity, ok := pvc.Spec.Resources.Requests[corev1.ResourceStorage]; ok { capacity = quantity.String() } accessModes := make([]string, 0, len(pvc.Spec.AccessModes)) for _, mode := range pvc.Spec.AccessModes { accessModes = append(accessModes, string(mode)) } items = append(items, PVCSummary{ Namespace: pvc.Namespace, Name: pvc.Name, VolumeName: pvc.Spec.VolumeName, Phase: string(pvc.Status.Phase), StorageClass: storageClass, Capacity: capacity, AccessModes: accessModes, }) } sort.Slice(items, func(i, j int) bool { if items[i].Namespace == items[j].Namespace { return items[i].Name < items[j].Name } return items[i].Namespace < items[j].Namespace }) return items, nil } // PersistentVolumeClaimExists reports whether a PVC can be read successfully. func (c *Client) PersistentVolumeClaimExists(ctx context.Context, namespace, pvcName string) (bool, error) { _, err := c.Clientset.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, pvcName, metav1.GetOptions{}) if err == nil { return true, nil } if apierrors.IsNotFound(err) { return false, nil } return false, fmt.Errorf("get pvc %s/%s: %w", namespace, pvcName, err) }