package k8s import ( "context" "encoding/json" "fmt" "gitea.loveuer.com/loveuer/cluster/internal/model" "gitea.loveuer.com/loveuer/cluster/pkg/resp" "gitea.loveuer.com/loveuer/cluster/pkg/store" "github.com/gofiber/fiber/v3" "gorm.io/gorm" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/yaml" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func getK8sClient(db *gorm.DB) (*kubernetes.Clientset, error) { var config model.ClusterConfig if err := db.Where("key = ?", "kubeconfig").First(&config).Error; err != nil { return nil, fmt.Errorf("kubeconfig not found: %w", err) } if config.Value == "" { return nil, fmt.Errorf("kubeconfig is empty") } clientConfig, err := clientcmd.RESTConfigFromKubeConfig([]byte(config.Value)) if err != nil { return nil, fmt.Errorf("failed to parse kubeconfig: %w", err) } clientset, err := kubernetes.NewForConfig(clientConfig) if err != nil { return nil, fmt.Errorf("failed to create k8s client: %w", err) } return clientset, nil } func K8sNamespaceList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler { return func(c fiber.Ctx) error { clientset, err := getK8sClient(db) if err != nil { return resp.R500(c, "", nil, err) } namespaces, err := clientset.CoreV1().Namespaces().List(c.Context(), metav1.ListOptions{}) if err != nil { return resp.R500(c, "", nil, err) } return resp.R200(c, map[string]any{ "items": namespaces.Items, }) } } func K8sDeploymentList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler { return func(c fiber.Ctx) error { namespace := c.Query("namespace", "") clientset, err := getK8sClient(db) if err != nil { return resp.R500(c, "", nil, err) } deployments, err := clientset.AppsV1().Deployments(namespace).List(c.Context(), metav1.ListOptions{}) if err != nil { return resp.R500(c, "", nil, err) } return resp.R200(c, map[string]any{ "items": deployments.Items, }) } } func K8sStatefulSetList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler { return func(c fiber.Ctx) error { namespace := c.Query("namespace", "") clientset, err := getK8sClient(db) if err != nil { return resp.R500(c, "", nil, err) } statefulsets, err := clientset.AppsV1().StatefulSets(namespace).List(c.Context(), metav1.ListOptions{}) if err != nil { return resp.R500(c, "", nil, err) } return resp.R200(c, map[string]any{ "items": statefulsets.Items, }) } } func K8sConfigMapList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler { return func(c fiber.Ctx) error { namespace := c.Query("namespace", "") clientset, err := getK8sClient(db) if err != nil { return resp.R500(c, "", nil, err) } configmaps, err := clientset.CoreV1().ConfigMaps(namespace).List(c.Context(), metav1.ListOptions{}) if err != nil { return resp.R500(c, "", nil, err) } return resp.R200(c, map[string]any{ "items": configmaps.Items, }) } } func K8sPodList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler { return func(c fiber.Ctx) error { namespace := c.Query("namespace", "") clientset, err := getK8sClient(db) if err != nil { return resp.R500(c, "", nil, err) } pods, err := clientset.CoreV1().Pods(namespace).List(c.Context(), metav1.ListOptions{}) if err != nil { return resp.R500(c, "", nil, err) } return resp.R200(c, map[string]any{ "items": pods.Items, }) } } func K8sPVList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler { return func(c fiber.Ctx) error { clientset, err := getK8sClient(db) if err != nil { return resp.R500(c, "", nil, err) } pvs, err := clientset.CoreV1().PersistentVolumes().List(c.Context(), metav1.ListOptions{}) if err != nil { return resp.R500(c, "", nil, err) } return resp.R200(c, map[string]any{ "items": pvs.Items, }) } } func K8sPVCList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler { return func(c fiber.Ctx) error { namespace := c.Query("namespace", "") clientset, err := getK8sClient(db) if err != nil { return resp.R500(c, "", nil, err) } pvcs, err := clientset.CoreV1().PersistentVolumeClaims(namespace).List(c.Context(), metav1.ListOptions{}) if err != nil { return resp.R500(c, "", nil, err) } return resp.R200(c, map[string]any{ "items": pvcs.Items, }) } } func K8sServiceList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler { return func(c fiber.Ctx) error { namespace := c.Query("namespace", "") clientset, err := getK8sClient(db) if err != nil { return resp.R500(c, "", nil, err) } services, err := clientset.CoreV1().Services(namespace).List(c.Context(), metav1.ListOptions{}) if err != nil { return resp.R500(c, "", nil, err) } return resp.R200(c, map[string]any{ "items": services.Items, }) } } func K8sResourceApply(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler { return func(c fiber.Ctx) error { var req struct { Yaml string `json:"yaml"` } if err := json.Unmarshal(c.Body(), &req); err != nil { return resp.R400(c, "", nil, err) } if req.Yaml == "" { return resp.R400(c, "", nil, fmt.Errorf("yaml content is empty")) } var config model.ClusterConfig if err := db.Where("key = ?", "kubeconfig").First(&config).Error; err != nil { return resp.R500(c, "", nil, fmt.Errorf("kubeconfig not found: %w", err)) } if config.Value == "" { return resp.R500(c, "", nil, fmt.Errorf("kubeconfig is empty")) } clientConfig, err := clientcmd.RESTConfigFromKubeConfig([]byte(config.Value)) if err != nil { return resp.R500(c, "", nil, fmt.Errorf("failed to parse kubeconfig: %w", err)) } dynamicClient, err := dynamic.NewForConfig(clientConfig) if err != nil { return resp.R500(c, "", nil, fmt.Errorf("failed to create dynamic client: %w", err)) } var obj unstructured.Unstructured if err := yaml.Unmarshal([]byte(req.Yaml), &obj); err != nil { return resp.R400(c, "", nil, fmt.Errorf("failed to parse yaml: %w", err)) } gvk := obj.GroupVersionKind() namespace := obj.GetNamespace() name := obj.GetName() if name == "" { return resp.R400(c, "", nil, fmt.Errorf("resource name is required")) } gvr := schema.GroupVersionResource{ Group: gvk.Group, Version: gvk.Version, Resource: getResourceName(gvk.Kind), } var result *unstructured.Unstructured if namespace != "" { result, err = dynamicClient.Resource(gvr).Namespace(namespace).Create(c.Context(), &obj, metav1.CreateOptions{}) } else { result, err = dynamicClient.Resource(gvr).Create(c.Context(), &obj, metav1.CreateOptions{}) } if err != nil { return resp.R500(c, "", nil, fmt.Errorf("failed to apply resource: %w", err)) } return resp.R200(c, map[string]any{ "name": result.GetName(), "namespace": result.GetNamespace(), "kind": result.GetKind(), }) } } func getResourceName(kind string) string { kindToResource := map[string]string{ "Namespace": "namespaces", "Deployment": "deployments", "StatefulSet": "statefulsets", "Service": "services", "ConfigMap": "configmaps", "Pod": "pods", "PersistentVolume": "persistentvolumes", "PersistentVolumeClaim": "persistentvolumeclaims", "Secret": "secrets", "Ingress": "ingresses", "DaemonSet": "daemonsets", "Job": "jobs", "CronJob": "cronjobs", "ReplicaSet": "replicasets", "ServiceAccount": "serviceaccounts", "Role": "roles", "RoleBinding": "rolebindings", "ClusterRole": "clusterroles", "ClusterRoleBinding": "clusterrolebindings", } if resource, ok := kindToResource[kind]; ok { return resource } return kind + "s" }