package handler import ( "bytes" "context" "fmt" corev1 "k8s.io/api/core/v1" storagev1 "k8s.io/api/storage/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/yaml" "github.com/goodrain/rainbond/pkg/component/k8s" ) // StorageClassInfo is the API response struct for a StorageClass type StorageClassInfo struct { Name string `json:"name"` Provisioner string `json:"provisioner"` IsDefault bool `json:"is_default"` ReclaimPolicy string `json:"reclaim_policy"` VolumeBindingMode string `json:"volume_binding_mode"` AllowVolumeExpansion bool `json:"allow_volume_expansion"` PVCount int `json:"pv_count"` } // StorageHandler handles StorageClass and PersistentVolume operations type StorageHandler struct{} // ListStorageClasses returns all StorageClasses with PV counts func (h *StorageHandler) ListStorageClasses() ([]StorageClassInfo, error) { client := k8s.Default().Clientset scList, err := client.StorageV1().StorageClasses().List(context.Background(), metav1.ListOptions{}) if err != nil { return nil, err } pvList, err := client.CoreV1().PersistentVolumes().List(context.Background(), metav1.ListOptions{}) if err != nil { return nil, err } pvCount := make(map[string]int) for _, pv := range pvList.Items { if pv.Spec.StorageClassName != "" { pvCount[pv.Spec.StorageClassName]++ } } var result []StorageClassInfo for _, sc := range scList.Items { isDefault := sc.Annotations["storageclass.kubernetes.io/is-default-class"] == "true" reclaimPolicy := "" if sc.ReclaimPolicy != nil { reclaimPolicy = string(*sc.ReclaimPolicy) } volumeBindingMode := "" if sc.VolumeBindingMode != nil { volumeBindingMode = string(*sc.VolumeBindingMode) } allowExpansion := sc.AllowVolumeExpansion != nil && *sc.AllowVolumeExpansion result = append(result, StorageClassInfo{ Name: sc.Name, Provisioner: sc.Provisioner, IsDefault: isDefault, ReclaimPolicy: reclaimPolicy, VolumeBindingMode: volumeBindingMode, AllowVolumeExpansion: allowExpansion, PVCount: pvCount[sc.Name], }) } return result, nil } // CreateStorageClass decodes YAML and creates a StorageClass func (h *StorageHandler) CreateStorageClass(yamlBody []byte) (*storagev1.StorageClass, error) { sc := &storagev1.StorageClass{} decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(yamlBody), 4096) if err := decoder.Decode(sc); err != nil { return nil, fmt.Errorf("invalid StorageClass YAML: %v", err) } return k8s.Default().Clientset.StorageV1().StorageClasses().Create(context.Background(), sc, metav1.CreateOptions{}) } // DeleteStorageClass deletes a StorageClass by name (no-op if not found) func (h *StorageHandler) DeleteStorageClass(name string) error { err := k8s.Default().Clientset.StorageV1().StorageClasses().Delete(context.Background(), name, metav1.DeleteOptions{}) if errors.IsNotFound(err) { return nil } return err } // PVInfo is the flat API response struct for a PersistentVolume type PVInfo struct { Name string `json:"name"` Capacity string `json:"capacity"` AccessModes []string `json:"access_modes"` StorageClass string `json:"storage_class"` Status string `json:"status"` ReclaimPolicy string `json:"reclaim_policy"` Claim string `json:"claim"` } // ListPersistentVolumes returns all PersistentVolumes as flat PVInfo structs func (h *StorageHandler) ListPersistentVolumes() ([]PVInfo, error) { list, err := k8s.Default().Clientset.CoreV1().PersistentVolumes().List(context.Background(), metav1.ListOptions{}) if err != nil { return nil, err } result := make([]PVInfo, 0, len(list.Items)) for _, pv := range list.Items { capacity := "" if q, ok := pv.Spec.Capacity[corev1.ResourceStorage]; ok { capacity = q.String() } modes := make([]string, 0, len(pv.Spec.AccessModes)) for _, m := range pv.Spec.AccessModes { modes = append(modes, string(m)) } claim := "" if pv.Spec.ClaimRef != nil { claim = pv.Spec.ClaimRef.Namespace + "/" + pv.Spec.ClaimRef.Name } reclaimPolicy := "" if pv.Spec.PersistentVolumeReclaimPolicy != "" { reclaimPolicy = string(pv.Spec.PersistentVolumeReclaimPolicy) } result = append(result, PVInfo{ Name: pv.Name, Capacity: capacity, AccessModes: modes, StorageClass: pv.Spec.StorageClassName, Status: string(pv.Status.Phase), ReclaimPolicy: reclaimPolicy, Claim: claim, }) } return result, nil } // CreatePersistentVolume decodes YAML and creates a PersistentVolume func (h *StorageHandler) CreatePersistentVolume(yamlBody []byte) (*corev1.PersistentVolume, error) { pv := &corev1.PersistentVolume{} decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(yamlBody), 4096) if err := decoder.Decode(pv); err != nil { return nil, fmt.Errorf("invalid PersistentVolume YAML: %v", err) } return k8s.Default().Clientset.CoreV1().PersistentVolumes().Create(context.Background(), pv, metav1.CreateOptions{}) } // DeletePersistentVolume deletes a PV by name (no-op if not found) func (h *StorageHandler) DeletePersistentVolume(name string) error { err := k8s.Default().Clientset.CoreV1().PersistentVolumes().Delete(context.Background(), name, metav1.DeleteOptions{}) if errors.IsNotFound(err) { return nil } return err } var storageHandler *StorageHandler // GetStorageHandler returns the singleton StorageHandler func GetStorageHandler() *StorageHandler { if storageHandler == nil { storageHandler = &StorageHandler{} } return storageHandler }