feat: add k8s cluster resource management module
- Add K8s module with kubeconfig storage in cluster_config table - Implement resource list APIs for 8 kinds: namespace, deployment, statefulset, service, configmap, pod, pv, pvc - Add K8sResourceList page with sidebar navigation and resource tables - Support YAML upload/input dialog for resource creation via dynamic client - Add kubeconfig settings drawer - Increase main content width from lg to xl for better table display - Add navigation link for cluster resources in top bar 🤖 Generated with [Qoder][https://qoder.com]
This commit is contained in:
301
internal/module/k8s/handler.resource.go
Normal file
301
internal/module/k8s/handler.resource.go
Normal file
@@ -0,0 +1,301 @@
|
||||
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"
|
||||
}
|
||||
Reference in New Issue
Block a user