2026-02-06 18:25:19 -03:00
|
|
|
package k8s
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
2026-04-12 11:09:49 -03:00
|
|
|
"sort"
|
2026-02-06 18:25:19 -03:00
|
|
|
|
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
2026-04-12 11:09:49 -03:00
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
2026-02-06 18:25:19 -03:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
|
)
|
|
|
|
|
|
2026-04-12 11:09:49 -03:00
|
|
|
type PVCSummary struct {
|
|
|
|
|
Namespace string
|
|
|
|
|
Name string
|
|
|
|
|
VolumeName string
|
|
|
|
|
Phase string
|
|
|
|
|
StorageClass string
|
|
|
|
|
Capacity string
|
|
|
|
|
AccessModes []string
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-06 18:25:19 -03:00
|
|
|
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
|
|
|
|
|
}
|
2026-04-12 11:09:49 -03:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|