soteria/internal/k8s/volumes.go

104 lines
3.1 KiB
Go

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)
}