package handlers import ( "net/http" "strconv" "ezops/internal/models" k8sclient "ezops/pkg/kubernetesclient" "github.com/gin-gonic/gin" ) func (h *KubernetesHandler) ListHPAs(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: err.Error()}) return } hpas, err := client.ListHPAs() if err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "K8s API error: " + err.Error()}) return } c.JSON(http.StatusOK, models.Response{Code: 200, Data: hpas.Items}) } // CreateHPA 创建 HPA func (h *KubernetesHandler) CreateHPA(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) var req k8sclient.CreateHPARequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "参数错误: " + err.Error()}) return } if req.Name == "" || req.Namespace == "" || req.TargetName == "" { c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "name/namespace/targetName 必填"}) return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: err.Error()}) return } hpa, err := client.CreateHPA(req) if err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "创建 HPA 失败: " + err.Error()}) return } c.JSON(http.StatusOK, models.Response{Code: 200, Message: "HPA 创建成功", Data: hpa}) } // DeleteHPA 删除 HPA func (h *KubernetesHandler) DeleteHPA(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, models.Response{Code: 500, Message: err.Error()}) return } if err := client.DeleteHPA(namespace, name); err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "删除 HPA 失败: " + err.Error()}) return } c.JSON(http.StatusOK, models.Response{Code: 200, Message: "HPA 已删除"}) } // ============================================================================ // CronJob 管理 // ============================================================================ // ListCronJobs 获取 CronJob 列表 func (h *KubernetesHandler) ListCronJobs(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: err.Error()}) return } cronjobs, err := client.ListCronJobs() if err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "K8s API error: " + err.Error()}) return } c.JSON(http.StatusOK, models.Response{Code: 200, Data: cronjobs.Items}) } // CreateCronJob 创建 CronJob func (h *KubernetesHandler) CreateCronJob(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) var req k8sclient.CreateCronJobRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "参数错误: " + err.Error()}) return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: err.Error()}) return } cj, err := client.CreateCronJob(req) if err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "创建 CronJob 失败: " + err.Error()}) return } c.JSON(http.StatusOK, models.Response{Code: 200, Message: "CronJob 创建成功", Data: cj}) } // SuspendCronJob 暂停/恢复 CronJob func (h *KubernetesHandler) SuspendCronJob(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Param("namespace") name := c.Param("name") var req struct { Suspend bool `json:"suspend"` } c.ShouldBindJSON(&req) client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: err.Error()}) return } if err := client.SuspendCronJob(namespace, name, req.Suspend); err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "操作失败: " + err.Error()}) return } action := "恢复" if req.Suspend { action = "暂停" } c.JSON(http.StatusOK, models.Response{Code: 200, Message: "CronJob 已" + action}) } // DeleteCronJob 删除 CronJob func (h *KubernetesHandler) DeleteCronJob(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, models.Response{Code: 500, Message: err.Error()}) return } if err := client.DeleteCronJob(namespace, name); err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "删除 CronJob 失败: " + err.Error()}) return } c.JSON(http.StatusOK, models.Response{Code: 200, Message: "CronJob 已删除"}) } // TriggerCronJob 手动触发 CronJob func (h *KubernetesHandler) TriggerCronJob(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, models.Response{Code: 500, Message: err.Error()}) return } if err := client.TriggerCronJob(namespace, name); err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "触发失败: " + err.Error()}) return } c.JSON(http.StatusOK, models.Response{Code: 200, Message: "手动触发成功"}) } // ============================================================================ // Deployment CI 集成 (Webhook 更新镜像) // ============================================================================ // UpdateDeploymentImage 更新 Deployment 容器镜像 (CI webhook) func (h *KubernetesHandler) ListHPAsNS(c *gin.Context) { h.listHPAsWithNS(c, c.Param("namespace")) } func (h *KubernetesHandler) CreateHPANS(c *gin.Context) { h.createHPAWithNS(c, c.Param("namespace")) } func (h *KubernetesHandler) DeleteHPANS(c *gin.Context) { h.deleteHPAWithNS(c, c.Param("namespace")) } func (h *KubernetesHandler) ListCronJobsNS(c *gin.Context) { h.listCronJobsWithNS(c, c.Param("namespace")) } func (h *KubernetesHandler) CreateCronJobNS(c *gin.Context) { h.createCronJobWithNS(c, c.Param("namespace")) } func (h *KubernetesHandler) SuspendCronJobNS(c *gin.Context) { h.suspendCronJobWithNS(c, c.Param("namespace")) } func (h *KubernetesHandler) TriggerCronJobNS(c *gin.Context) { h.triggerCronJobWithNS(c, c.Param("namespace")) } func (h *KubernetesHandler) DeleteCronJobNS(c *gin.Context) { h.deleteCronJobWithNS(c, c.Param("namespace")) } func (h *KubernetesHandler) listHPAsWithNS(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, models.Response{Code: 500, Message: err.Error()}); return } hpas, err := client.ListHPAs() if err != nil { c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return } type HPASummary struct { Namespace string `json:"namespace"` Name string `json:"name"` TargetKind string `json:"target_kind"` TargetName string `json:"target_name"` MinReplicas int `json:"min_replicas"` MaxReplicas int `json:"max_replicas"` CPUPercent int `json:"cpu_percent"` MemPercent int `json:"mem_percent"` CurrentReplicas int `json:"current_replicas"` } result := []HPASummary{} for _, h := range hpas.Items { if h.Metadata.Namespace != ns { continue } cpu := 0; mem := 0 for _, m := range h.Spec.Metrics { if m.Resource != nil { if m.Resource.Name == "cpu" { cpu = m.Resource.Target.AverageUtilization } if m.Resource.Name == "memory" { mem = m.Resource.Target.AverageUtilization } } } result = append(result, HPASummary{ Namespace: h.Metadata.Namespace, Name: h.Metadata.Name, TargetKind: h.Spec.ScaleTargetRef.Kind, TargetName: h.Spec.ScaleTargetRef.Name, MinReplicas: h.Spec.MinReplicas, MaxReplicas: h.Spec.MaxReplicas, CPUPercent: cpu, MemPercent: mem, CurrentReplicas: h.Status.CurrentReplicas, }) } c.JSON(200, models.Response{Code: 200, Data: result}) } func (h *KubernetesHandler) createHPAWithNS(c *gin.Context, ns string) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } var req k8sclient.CreateHPARequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, models.Response{Code: 400, Message: "参数错误"}); return } if req.Namespace == "" { req.Namespace = ns } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return } hpa, err := client.CreateHPA(req) if err != nil { c.JSON(500, models.Response{Code: 500, Message: "创建失败: " + err.Error()}); return } c.JSON(200, models.Response{Code: 200, Data: hpa}) } func (h *KubernetesHandler) deleteHPAWithNS(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, models.Response{Code: 500, Message: err.Error()}); return } if err := client.DeleteHPA(ns, c.Param("name")); err != nil { c.JSON(500, models.Response{Code: 500, Message: "删除失败: " + err.Error()}); return } c.JSON(200, models.Response{Code: 200, Message: "已删除"}) } func (h *KubernetesHandler) listCronJobsWithNS(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, models.Response{Code: 500, Message: err.Error()}); return } cjs, err := client.ListCronJobs() if err != nil { c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return } filtered := []k8sclient.CronJobItem{} for _, cj := range cjs.Items { if cj.Metadata.Namespace == ns { filtered = append(filtered, cj) } } c.JSON(200, models.Response{Code: 200, Data: filtered}) } func (h *KubernetesHandler) createCronJobWithNS(c *gin.Context, ns string) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } var req k8sclient.CreateCronJobRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, models.Response{Code: 400, Message: "参数错误"}); return } if req.Namespace == "" { req.Namespace = ns } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return } cj, err := client.CreateCronJob(req) if err != nil { c.JSON(500, models.Response{Code: 500, Message: "创建失败: " + err.Error()}); return } c.JSON(200, models.Response{Code: 200, Data: cj}) } func (h *KubernetesHandler) suspendCronJobWithNS(c *gin.Context, ns string) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } var req struct { Suspend bool `json:"suspend"` }; c.ShouldBindJSON(&req) client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return } if err := client.SuspendCronJob(ns, c.Param("name"), req.Suspend); err != nil { c.JSON(500, models.Response{Code: 500, Message: "操作失败: " + err.Error()}); return } action := "恢复"; if req.Suspend { action = "暂停" } c.JSON(200, models.Response{Code: 200, Message: "CronJob 已" + action}) } func (h *KubernetesHandler) triggerCronJobWithNS(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, models.Response{Code: 500, Message: err.Error()}); return } if err := client.TriggerCronJob(ns, c.Param("name")); err != nil { c.JSON(500, models.Response{Code: 500, Message: "触发失败: " + err.Error()}); return } c.JSON(200, models.Response{Code: 200, Message: "手动触发成功"}) } func (h *KubernetesHandler) deleteCronJobWithNS(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, models.Response{Code: 500, Message: err.Error()}); return } if err := client.DeleteCronJob(ns, c.Param("name")); err != nil { c.JSON(500, models.Response{Code: 500, Message: "删除失败: " + err.Error()}); return } c.JSON(200, models.Response{Code: 200, Message: "已删除"}) } func (h *KubernetesHandler) ListHPAsAllNS(c *gin.Context) { clusterID := h.resolveClusterCode(c.Param("clusterCode")) if clusterID == 0 { c.JSON(500, models.Response{Code: 500, Message: "cluster not found"}); return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return } hpas, err := client.ListHPAs() if err != nil { c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return } type HPASummary struct { Namespace string `json:"namespace"` Name string `json:"name"` TargetKind string `json:"target_kind"` TargetName string `json:"target_name"` MinReplicas int `json:"min_replicas"` MaxReplicas int `json:"max_replicas"` CPUPercent int `json:"cpu_percent"` MemPercent int `json:"mem_percent"` CurrentReplicas int `json:"current_replicas"` } result := []HPASummary{} for _, h := range hpas.Items { cpu := 0; mem := 0 for _, m := range h.Spec.Metrics { if m.Resource != nil { if m.Resource.Name == "cpu" { cpu = m.Resource.Target.AverageUtilization } if m.Resource.Name == "memory" { mem = m.Resource.Target.AverageUtilization } } } result = append(result, HPASummary{ Namespace: h.Metadata.Namespace, Name: h.Metadata.Name, TargetKind: h.Spec.ScaleTargetRef.Kind, TargetName: h.Spec.ScaleTargetRef.Name, MinReplicas: h.Spec.MinReplicas, MaxReplicas: h.Spec.MaxReplicas, CPUPercent: cpu, MemPercent: mem, CurrentReplicas: h.Status.CurrentReplicas, }) } c.JSON(200, models.Response{Code: 200, Data: result}) } // ============================================================================ // Helpers // ============================================================================ // buildPodMetricsMap fetches pod metrics and returns a map keyed by "namespace/podname" → aggregated CPU/Memory strings. // Best-effort: returns empty map if metrics-server is unavailable. // Logan.Li M2