package controller import ( "fmt" "net/http" "sort" "strconv" "strings" "ezops/model" "github.com/gin-gonic/gin" ) func (h *KubernetesHandler) ListDeployments(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Query("namespace") client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: err.Error()}) return } deps, err := client.ListDeployments() if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}) return } type DepSummary struct { Namespace string `json:"namespace"` Name string `json:"name"` Replicas int `json:"replicas"` AvailableReplicas int `json:"available_replicas"` ReadyReplicas int `json:"ready_replicas"` Image string `json:"image"` } result := []DepSummary{} for _, d := range deps.Items { if namespace != "" && d.Metadata.Namespace != namespace { continue } image := "" if len(d.Spec.Template.Spec.Containers) > 0 { image = d.Spec.Template.Spec.Containers[0].Image } result = append(result, DepSummary{ Namespace: d.Metadata.Namespace, Name: d.Metadata.Name, Replicas: d.Spec.Replicas, AvailableReplicas: d.Status.AvailableReplicas, ReadyReplicas: d.Status.ReadyReplicas, Image: image, }) } c.JSON(http.StatusOK, model.Response{Code: 200, Message: "success", Data: result}) } // ListServices 获取Service列表 // @Summary 获取Service列表 // @Description 获取指定集群的Service列表 // @Tags K8s集群 // @Produce json // @Security Bearer // @Param id path int true "集群ID" // @Param namespace query string false "命名空间" // @Success 200 {object} model.Response{data=[]model.KubernetesService} // @Router /kubernetes-clusters/{id}/services [get] func (h *KubernetesHandler) ScaleDeployment(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Param("namespace") name := c.Param("name") var req struct { Replicas int `json:"replicas"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "参数错误"}) return } if req.Replicas < 0 { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "副本数不能为负数"}) return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: err.Error()}) return } if err := client.ScaleDeployment(namespace, name, req.Replicas); err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "扩缩容失败: " + err.Error()}) return } c.JSON(http.StatusOK, model.Response{Code: 200, Message: fmt.Sprintf("已设置为 %d 副本", req.Replicas)}) } // RestartDeployment 重启 Deployment (滚动重启) func (h *KubernetesHandler) RestartDeployment(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Param("namespace") name := c.Param("name") client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: err.Error()}) return } if err := client.RestartDeployment(namespace, name); err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "重启失败: " + err.Error()}) return } c.JSON(http.StatusOK, model.Response{Code: 200, Message: "重启指令已下发"}) } // PauseDeployment 暂停 Deployment 滚动更新(spec.paused=true) func (h *KubernetesHandler) PauseDeployment(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Param("namespace") name := c.Param("name") client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: err.Error()}) return } if err := client.PauseDeployment(namespace, name); err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "暂停更新失败: " + err.Error()}) return } c.JSON(http.StatusOK, model.Response{Code: 200, Message: "已暂停滚动更新"}) } // ResumeDeployment 恢复 Deployment 滚动更新(spec.paused=false) func (h *KubernetesHandler) ResumeDeployment(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Param("namespace") name := c.Param("name") client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: err.Error()}) return } if err := client.ResumeDeployment(namespace, name); err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "恢复更新失败: " + err.Error()}) return } c.JSON(http.StatusOK, model.Response{Code: 200, Message: "已恢复滚动更新"}) } // ============================================================================ // HPA 管理 // ============================================================================ // ListHPAs 获取 HPA 列表 func (h *KubernetesHandler) UpdateDeploymentImage(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Param("namespace") name := c.Param("name") var req struct { Image string `json:"image"` ContainerName string `json:"container_name"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "参数错误"}) return } if req.Image == "" { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "image 不能为空"}) return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: err.Error()}) return } if err := client.UpdateDeploymentImage(namespace, name, req.ContainerName, req.Image); err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "更新镜像失败: " + err.Error()}) return } c.JSON(http.StatusOK, model.Response{Code: 200, Message: fmt.Sprintf("镜像已更新为 %s,正在滚动更新...", req.Image)}) } // ============================================================================ // Namespace // ============================================================================ // ListNamespaces 获取命名空间列表 func (h *KubernetesHandler) ListDeploymentsNS(c *gin.Context) { h.listDeploymentsWithNS(c, c.Param("namespace")) } // GetDeploymentNS 获取单个 Deployment 详情(避免前端 404)// Logan.Li func (h *KubernetesHandler) GetDeploymentNS(c *gin.Context) { ns := c.Param("namespace") name := c.Param("name") clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } deps, err := client.ListDeployments() if err != nil { c.JSON(500, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return } for _, d := range deps.Items { if d.Metadata.Namespace == ns && d.Metadata.Name == name { image := "" if len(d.Spec.Template.Spec.Containers) > 0 { image = d.Spec.Template.Spec.Containers[0].Image } c.JSON(200, model.Response{Code: 200, Data: map[string]interface{}{ "namespace": d.Metadata.Namespace, "name": d.Metadata.Name, "replicas": d.Spec.Replicas, "available_replicas": d.Status.AvailableReplicas, "ready_replicas": d.Status.ReadyReplicas, "image": image, "paused": d.Spec.Paused, }}) return } } c.JSON(404, model.Response{Code: 404, Message: "Deployment not found"}) } func (h *KubernetesHandler) ScaleDeploymentNS(c *gin.Context) { h.scaleDeploymentWithNS(c, c.Param("namespace")) } func (h *KubernetesHandler) RestartDeploymentNS(c *gin.Context) { h.restartDeploymentWithNS(c, c.Param("namespace")) } func (h *KubernetesHandler) UpdateDeploymentImageNS(c *gin.Context) { h.updateDeploymentImageWithNS(c, c.Param("namespace")) } func (h *KubernetesHandler) PauseDeploymentNS(c *gin.Context) { h.pauseDeploymentWithNS(c, c.Param("namespace")) } func (h *KubernetesHandler) ResumeDeploymentNS(c *gin.Context) { h.resumeDeploymentWithNS(c, c.Param("namespace")) } func (h *KubernetesHandler) listDeploymentsWithNS(c *gin.Context, ns string) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } deps, err := client.ListDeployments() if err != nil { c.JSON(500, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return } type DepSummary struct { Namespace string `json:"namespace"` Name string `json:"name"` Replicas int `json:"replicas"` AvailableReplicas int `json:"available_replicas"` ReadyReplicas int `json:"ready_replicas"` Image string `json:"image"` Paused bool `json:"paused"` Selector map[string]string `json:"selector,omitempty"` // matchLabels — 前端 Service Tab 关联用 } result := []DepSummary{} for _, d := range deps.Items { if d.Metadata.Namespace != ns { continue } image := ""; if len(d.Spec.Template.Spec.Containers) > 0 { image = d.Spec.Template.Spec.Containers[0].Image } result = append(result, DepSummary{ Namespace: d.Metadata.Namespace, Name: d.Metadata.Name, Replicas: d.Spec.Replicas, AvailableReplicas: d.Status.AvailableReplicas, ReadyReplicas: d.Status.ReadyReplicas, Image: image, Paused: d.Spec.Paused, Selector: func() map[string]string { if d.Spec.Selector != nil { return d.Spec.Selector.MatchLabels }; return nil }(), }) } c.JSON(200, model.Response{Code: 200, Data: result}) } func (h *KubernetesHandler) scaleDeploymentWithNS(c *gin.Context, ns string) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } var req struct { Replicas int `json:"replicas"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, model.Response{Code: 400, Message: "参数错误"}); return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } if err := client.ScaleDeployment(ns, c.Param("name"), req.Replicas); err != nil { c.JSON(500, model.Response{Code: 500, Message: "扩缩容失败: " + err.Error()}); return } c.JSON(200, model.Response{Code: 200, Message: fmt.Sprintf("已设置为 %d 副本", req.Replicas)}) } func (h *KubernetesHandler) restartDeploymentWithNS(c *gin.Context, ns string) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } if err := client.RestartDeployment(ns, c.Param("name")); err != nil { c.JSON(500, model.Response{Code: 500, Message: "重启失败: " + err.Error()}); return } c.JSON(200, model.Response{Code: 200, Message: "重启指令已下发"}) } func (h *KubernetesHandler) updateDeploymentImageWithNS(c *gin.Context, ns string) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } var req struct { Image string `json:"image"`; ContainerName string `json:"container_name"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, model.Response{Code: 400, Message: "参数错误"}); return } if strings.TrimSpace(req.Image) == "" { c.JSON(400, model.Response{Code: 400, Message: "image 不能为空"}); return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } if err := client.UpdateDeploymentImage(ns, c.Param("name"), req.ContainerName, req.Image); err != nil { c.JSON(500, model.Response{Code: 500, Message: "更新镜像失败: " + err.Error()}); return } c.JSON(200, model.Response{Code: 200, Message: fmt.Sprintf("镜像已更新为 %s", req.Image)}) } func (h *KubernetesHandler) pauseDeploymentWithNS(c *gin.Context, ns string) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } if err := client.PauseDeployment(ns, c.Param("name")); err != nil { c.JSON(500, model.Response{Code: 500, Message: "暂停更新失败: " + err.Error()}); return } c.JSON(200, model.Response{Code: 200, Message: "已暂停滚动更新"}) } func (h *KubernetesHandler) resumeDeploymentWithNS(c *gin.Context, ns string) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } if err := client.ResumeDeployment(ns, c.Param("name")); err != nil { c.JSON(500, model.Response{Code: 500, Message: "恢复更新失败: " + err.Error()}); return } c.JSON(200, model.Response{Code: 200, Message: "已恢复滚动更新"}) } func (h *KubernetesHandler) ListDeploymentsAllNS(c *gin.Context) { clusterID := h.resolveClusterCode(c.Param("clusterCode")) if clusterID == 0 { c.JSON(500, model.Response{Code: 500, Message: "cluster not found"}); return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } deps, err := client.ListDeployments() if err != nil { c.JSON(500, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return } type DepSummary struct { Namespace string `json:"namespace"` Name string `json:"name"` Replicas int `json:"replicas"` AvailableReplicas int `json:"available_replicas"` ReadyReplicas int `json:"ready_replicas"` Image string `json:"image"` } result := []DepSummary{} for _, d := range deps.Items { image := ""; if len(d.Spec.Template.Spec.Containers) > 0 { image = d.Spec.Template.Spec.Containers[0].Image } result = append(result, DepSummary{ Namespace: d.Metadata.Namespace, Name: d.Metadata.Name, Replicas: d.Spec.Replicas, AvailableReplicas: d.Status.AvailableReplicas, ReadyReplicas: d.Status.ReadyReplicas, Image: image, }) } c.JSON(200, model.Response{Code: 200, Data: result}) } // ListPodsAllNS lists all pods across all namespaces using clusterCode. func (h *KubernetesHandler) ListStatefulSetsNS(c *gin.Context) { clusterID := h.resolveClusterCode(c.Param("clusterCode")) if clusterID == 0 { c.JSON(500, model.Response{Code: 500, Message: "cluster not found"}); return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } sts, err := client.ListStatefulSets() if err != nil { c.JSON(500, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return } ns := c.Param("namespace") type StsSummary struct { Namespace string `json:"namespace"` Name string `json:"name"` Replicas int `json:"replicas"` Ready int `json:"ready"` Image string `json:"image"` } result := []StsSummary{} for _, s := range sts.Items { if s.Metadata.Namespace != ns { continue } image := ""; if len(s.Spec.Template.Spec.Containers) > 0 { image = s.Spec.Template.Spec.Containers[0].Image } result = append(result, StsSummary{ Namespace: s.Metadata.Namespace, Name: s.Metadata.Name, Replicas: s.Spec.Replicas, Ready: s.Status.ReadyReplicas, Image: image, }) } c.JSON(200, model.Response{Code: 200, Data: result}) } // ListDaemonSetsNS lists DaemonSets in a namespace func (h *KubernetesHandler) ListDaemonSetsNS(c *gin.Context) { clusterID := h.resolveClusterCode(c.Param("clusterCode")) if clusterID == 0 { c.JSON(500, model.Response{Code: 500, Message: "cluster not found"}); return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } ds, err := client.ListDaemonSets() if err != nil { c.JSON(500, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return } ns := c.Param("namespace") type DsSummary struct { Namespace string `json:"namespace"` Name string `json:"name"` Desired int `json:"desired"` Current int `json:"current"` Ready int `json:"ready"` Image string `json:"image"` } result := []DsSummary{} for _, d := range ds.Items { if d.Metadata.Namespace != ns { continue } image := ""; if len(d.Spec.Template.Spec.Containers) > 0 { image = d.Spec.Template.Spec.Containers[0].Image } result = append(result, DsSummary{ Namespace: d.Metadata.Namespace, Name: d.Metadata.Name, Desired: d.Status.DesiredNumberScheduled, Current: d.Status.CurrentNumberScheduled, Ready: d.Status.NumberReady, Image: image, }) } c.JSON(200, model.Response{Code: 200, Data: result}) } // ScaleStatefulSetNS scales a StatefulSet (Kuboard code route) func (h *KubernetesHandler) ScaleStatefulSetNS(c *gin.Context) { clusterID := h.resolveClusterCode(c.Param("clusterCode")) if clusterID == 0 { c.JSON(500, model.Response{Code: 500, Message: "cluster not found"}); return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } ns := c.Param("namespace") name := c.Param("name") var req struct{ Replicas int `json:"replicas"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, model.Response{Code: 400, Message: "参数错误"}); return } if err := client.ScaleStatefulSet(ns, name, req.Replicas); err != nil { c.JSON(500, model.Response{Code: 500, Message: "扩缩容失败: " + err.Error()}); return } c.JSON(200, model.Response{Code: 200, Message: "StatefulSet 副本数已更新为 " + strconv.Itoa(req.Replicas)}) } // ScaleStatefulSet scales a StatefulSet (ID route) func (h *KubernetesHandler) ScaleStatefulSet(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } if clusterID == 0 { c.JSON(500, model.Response{Code: 500, Message: "cluster not found"}); return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } ns := c.Param("namespace") name := c.Param("name") var req struct{ Replicas int `json:"replicas"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, model.Response{Code: 400, Message: "参数错误"}); return } if err := client.ScaleStatefulSet(ns, name, req.Replicas); err != nil { c.JSON(500, model.Response{Code: 500, Message: "扩缩容失败: " + err.Error()}); return } c.JSON(200, model.Response{Code: 200, Message: "StatefulSet 副本数已更新为 " + strconv.Itoa(req.Replicas)}) } // RestartStatefulSetNS restarts a StatefulSet (Kuboard code route) func (h *KubernetesHandler) RestartStatefulSetNS(c *gin.Context) { clusterID := h.resolveClusterCode(c.Param("clusterCode")) if clusterID == 0 { c.JSON(500, model.Response{Code: 500, Message: "cluster not found"}); return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } ns := c.Param("namespace") name := c.Param("name") if err := client.RestartStatefulSet(ns, name); err != nil { c.JSON(500, model.Response{Code: 500, Message: "重启失败: " + err.Error()}); return } c.JSON(200, model.Response{Code: 200, Message: "StatefulSet 滚动重启已触发"}) } // RestartStatefulSet restarts a StatefulSet (ID route) func (h *KubernetesHandler) RestartStatefulSet(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } if clusterID == 0 { c.JSON(500, model.Response{Code: 500, Message: "cluster not found"}); return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } ns := c.Param("namespace") name := c.Param("name") if err := client.RestartStatefulSet(ns, name); err != nil { c.JSON(500, model.Response{Code: 500, Message: "重启失败: " + err.Error()}); return } c.JSON(200, model.Response{Code: 200, Message: "StatefulSet 滚动重启已触发"}) } // RestartDaemonSetNS restarts a DaemonSet (Kuboard code route) func (h *KubernetesHandler) RestartDaemonSetNS(c *gin.Context) { clusterID := h.resolveClusterCode(c.Param("clusterCode")) if clusterID == 0 { c.JSON(500, model.Response{Code: 500, Message: "cluster not found"}); return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } ns := c.Param("namespace") name := c.Param("name") if err := client.RestartDaemonSet(ns, name); err != nil { c.JSON(500, model.Response{Code: 500, Message: "重启失败: " + err.Error()}); return } c.JSON(200, model.Response{Code: 200, Message: "DaemonSet 滚动重启已触发"}) } // RestartDaemonSet restarts a DaemonSet (ID route) func (h *KubernetesHandler) RestartDaemonSet(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } if clusterID == 0 { c.JSON(500, model.Response{Code: 500, Message: "cluster not found"}); return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } ns := c.Param("namespace") name := c.Param("name") if err := client.RestartDaemonSet(ns, name); err != nil { c.JSON(500, model.Response{Code: 500, Message: "重启失败: " + err.Error()}); return } c.JSON(200, model.Response{Code: 200, Message: "DaemonSet 滚动重启已触发"}) } // GetStatefulSetDetailNS returns a single StatefulSet detail (Kuboard code route) func (h *KubernetesHandler) GetStatefulSetDetailNS(c *gin.Context) { clusterID := h.resolveClusterCode(c.Param("clusterCode")) if clusterID == 0 { c.JSON(500, model.Response{Code: 500, Message: "cluster not found"}); return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } ns := c.Param("namespace") name := c.Param("name") sts, err := client.GetStatefulSet(ns, name) if err != nil { c.JSON(500, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return } c.JSON(200, model.Response{Code: 200, Data: sts}) } // GetDaemonSetDetailNS returns a single DaemonSet detail (Kuboard code route) func (h *KubernetesHandler) GetDaemonSetDetailNS(c *gin.Context) { clusterID := h.resolveClusterCode(c.Param("clusterCode")) if clusterID == 0 { c.JSON(500, model.Response{Code: 500, Message: "cluster not found"}); return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } ns := c.Param("namespace") name := c.Param("name") ds, err := client.GetDaemonSet(ns, name) if err != nil { c.JSON(500, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return } c.JSON(200, model.Response{Code: 200, Data: ds}) } // ============================================================================ // Pod Metrics (kubectl top pods) — per-container CPU/Memory usage via metrics-server // ============================================================================ // GetPodMetrics returns pod-level CPU/Memory metrics func (h *KubernetesHandler) ListReplicaSetsNS(c *gin.Context) { h.listReplicaSetsWithNS(c, c.Param("namespace")) } func (h *KubernetesHandler) listReplicaSetsWithNS(c *gin.Context, ns string) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } items, err := client.ListReplicaSetsRaw(ns) if err != nil { c.JSON(500, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return } type RSSummary struct { Namespace string `json:"namespace"` Name string `json:"name"` Replicas int `json:"replicas"` AvailableReplicas int `json:"available_replicas"` ReadyReplicas int `json:"ready_replicas"` Image string `json:"image"` CreatedAt string `json:"created_at"` Revision string `json:"revision"` OwnerDeployment string `json:"owner_deployment"` } result := []RSSummary{} for _, rs := range items { image := "" if len(rs.Spec.Template.Spec.Containers) > 0 { image = rs.Spec.Template.Spec.Containers[0].Image } revision := rs.Metadata.Annotations["deployment.kubernetes.io/revision"] owner := "" for _, o := range rs.Metadata.OwnerReferences { if o.Kind == "Deployment" { owner = o.Name; break } } result = append(result, RSSummary{ Namespace: rs.Metadata.Namespace, Name: rs.Metadata.Name, Replicas: rs.Spec.Replicas, AvailableReplicas: rs.Status.AvailableReplicas, ReadyReplicas: rs.Status.ReadyReplicas, Image: image, CreatedAt: rs.Metadata.CreationTimestamp, Revision: revision, OwnerDeployment: owner, }) } c.JSON(200, model.Response{Code: 200, Message: "success", Data: result}) } // ListDeploymentRevisionsNS Deployment 发布历史(按 revision 倒序) func (h *KubernetesHandler) ListDeploymentRevisionsNS(c *gin.Context) { h.listDeploymentRevisionsWithNS(c, c.Param("namespace")) } func (h *KubernetesHandler) listDeploymentRevisionsWithNS(c *gin.Context, ns string) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } deploymentName := c.Param("name") if deploymentName == "" { c.JSON(400, model.Response{Code: 400, Message: "缺少 deployment name"}); return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } items, err := client.ListReplicaSetsRaw(ns) if err != nil { c.JSON(500, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return } type Revision struct { Revision string `json:"revision"` ReplicaSet string `json:"replica_set"` Replicas int `json:"replicas"` Image string `json:"image"` CreatedAt string `json:"created_at"` Current bool `json:"current"` } result := []Revision{} for _, rs := range items { isOwner := false for _, o := range rs.Metadata.OwnerReferences { if o.Kind == "Deployment" && o.Name == deploymentName { isOwner = true; break } } if !isOwner { continue } image := "" if len(rs.Spec.Template.Spec.Containers) > 0 { image = rs.Spec.Template.Spec.Containers[0].Image } result = append(result, Revision{ Revision: rs.Metadata.Annotations["deployment.kubernetes.io/revision"], ReplicaSet: rs.Metadata.Name, Replicas: rs.Spec.Replicas, Image: image, CreatedAt: rs.Metadata.CreationTimestamp, Current: rs.Spec.Replicas > 0, }) } // 按 revision 数字倒序(最新在前) sort.Slice(result, func(i, j int) bool { ri, _ := strconv.Atoi(result[i].Revision) rj, _ := strconv.Atoi(result[j].Revision) return ri > rj }) c.JSON(200, model.Response{Code: 200, Message: "success", Data: result}) } // RollbackDeploymentNS 回滚 Deployment 到指定 revision 或指定 ReplicaSet func (h *KubernetesHandler) RollbackDeploymentNS(c *gin.Context) { h.rollbackDeploymentWithNS(c, c.Param("namespace")) } func (h *KubernetesHandler) rollbackDeploymentWithNS(c *gin.Context, ns string) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } deploymentName := c.Param("name") if deploymentName == "" { c.JSON(400, model.Response{Code: 400, Message: "缺少 deployment name"}); return } var req struct { Revision int `json:"revision"` ReplicaSet string `json:"replicaSet"` } _ = c.ShouldBindJSON(&req) // 允许空 body(但下面会校验) client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } targetRS := req.ReplicaSet // 通过 revision 定位 ReplicaSet if targetRS == "" && req.Revision > 0 { items, err := client.ListReplicaSetsRaw(ns) if err != nil { c.JSON(500, model.Response{Code: 500, Message: "获取 ReplicaSet 列表失败: " + err.Error()}); return } for _, rs := range items { isOwner := false for _, o := range rs.Metadata.OwnerReferences { if o.Kind == "Deployment" && o.Name == deploymentName { isOwner = true; break } } if !isOwner { continue } if rs.Metadata.Annotations["deployment.kubernetes.io/revision"] == strconv.Itoa(req.Revision) { targetRS = rs.Metadata.Name break } } if targetRS == "" { c.JSON(404, model.Response{Code: 404, Message: fmt.Sprintf("revision %d 的 ReplicaSet 未找到", req.Revision)}) return } } if targetRS == "" { c.JSON(400, model.Response{Code: 400, Message: "需要 revision 或 replicaSet 参数"}); return } // 取 ReplicaSet 的 template,patch 到 Deployment.spec.template 实现回滚 rsJSON, err := client.GetReplicaSet(ns, targetRS) if err != nil { c.JSON(500, model.Response{Code: 500, Message: "获取 ReplicaSet 失败: " + err.Error()}); return } specMap, ok := rsJSON["spec"].(map[string]interface{}) if !ok { c.JSON(500, model.Response{Code: 500, Message: "ReplicaSet 缺少 spec"}); return } template, ok := specMap["template"].(map[string]interface{}) if !ok { c.JSON(500, model.Response{Code: 500, Message: "ReplicaSet 缺少 template"}); return } if err := client.PatchDeploymentSpecTemplate(ns, deploymentName, template); err != nil { c.JSON(500, model.Response{Code: 500, Message: "回滚失败: " + err.Error()}); return } c.JSON(200, model.Response{ Code: 200, Message: fmt.Sprintf("Deployment %s 已回滚到 %s", deploymentName, targetRS), Data: gin.H{"replicaSet": targetRS, "revision": req.Revision}, }) } // ============================================================================ // Pod 删除 // Logan.Li // ============================================================================ // DeletePodNS 删除指定 Pod