Files
2026-07-09 14:16:56 +08:00

511 lines
16 KiB
Go

package controller
import (
"net/http"
"strconv"
"ezops/model"
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, model.Response{Code: 500, Message: err.Error()})
return
}
hpas, err := client.ListHPAs()
if err != nil {
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "K8s API error: " + err.Error()})
return
}
c.JSON(http.StatusOK, model.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, model.Response{Code: 400, Message: "参数错误: " + err.Error()})
return
}
if req.Name == "" || req.Namespace == "" || req.TargetName == "" {
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "name/namespace/targetName 必填"})
return
}
client, _, err := h.getK8sClient(clusterID)
if err != nil {
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: err.Error()})
return
}
hpa, err := client.CreateHPA(req)
if err != nil {
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "创建 HPA 失败: " + err.Error()})
return
}
c.JSON(http.StatusOK, model.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, model.Response{Code: 500, Message: err.Error()})
return
}
if err := client.DeleteHPA(namespace, name); err != nil {
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "删除 HPA 失败: " + err.Error()})
return
}
c.JSON(http.StatusOK, model.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, model.Response{Code: 500, Message: err.Error()})
return
}
cronjobs, err := client.ListCronJobs()
if err != nil {
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "K8s API error: " + err.Error()})
return
}
c.JSON(http.StatusOK, model.Response{Code: 200, Data: cronjobs.Items})
}
// ListCronJobsAllNS lists all CronJobs across all namespaces using clusterCode.
// Canonical route: GET /kubernetes/:clusterCode/cronjobs
func (h *KubernetesHandler) ListCronJobsAllNS(c *gin.Context) {
clusterID := h.resolveClusterCode(c.Param("clusterCode"))
if clusterID == 0 {
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "cluster not found"})
return
}
client, _, err := h.getK8sClient(clusterID)
if err != nil {
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: err.Error()})
return
}
cronjobs, err := client.ListCronJobs()
if err != nil {
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "K8s API error: " + err.Error()})
return
}
c.JSON(http.StatusOK, model.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, model.Response{Code: 400, Message: "参数错误: " + err.Error()})
return
}
client, _, err := h.getK8sClient(clusterID)
if err != nil {
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: err.Error()})
return
}
cj, err := client.CreateCronJob(req)
if err != nil {
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "创建 CronJob 失败: " + err.Error()})
return
}
c.JSON(http.StatusOK, model.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, model.Response{Code: 500, Message: err.Error()})
return
}
if err := client.SuspendCronJob(namespace, name, req.Suspend); err != nil {
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "操作失败: " + err.Error()})
return
}
action := "恢复"
if req.Suspend {
action = "暂停"
}
c.JSON(http.StatusOK, model.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, model.Response{Code: 500, Message: err.Error()})
return
}
if err := client.DeleteCronJob(namespace, name); err != nil {
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "删除 CronJob 失败: " + err.Error()})
return
}
c.JSON(http.StatusOK, model.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, model.Response{Code: 500, Message: err.Error()})
return
}
if err := client.TriggerCronJob(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: "手动触发成功"})
}
// ============================================================================
// 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, model.Response{Code: 500, Message: err.Error()})
return
}
hpas, err := client.ListHPAs()
if err != nil {
c.JSON(500, model.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, model.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, model.Response{Code: 400, Message: "参数错误"})
return
}
if req.Namespace == "" {
req.Namespace = ns
}
client, _, err := h.getK8sClient(clusterID)
if err != nil {
c.JSON(500, model.Response{Code: 500, Message: err.Error()})
return
}
hpa, err := client.CreateHPA(req)
if err != nil {
c.JSON(500, model.Response{Code: 500, Message: "创建失败: " + err.Error()})
return
}
c.JSON(200, model.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, model.Response{Code: 500, Message: err.Error()})
return
}
if err := client.DeleteHPA(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) 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, model.Response{Code: 500, Message: err.Error()})
return
}
cjs, err := client.ListCronJobs()
if err != nil {
c.JSON(500, model.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, model.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, model.Response{Code: 400, Message: "参数错误"})
return
}
if req.Namespace == "" {
req.Namespace = ns
}
client, _, err := h.getK8sClient(clusterID)
if err != nil {
c.JSON(500, model.Response{Code: 500, Message: err.Error()})
return
}
cj, err := client.CreateCronJob(req)
if err != nil {
c.JSON(500, model.Response{Code: 500, Message: "创建失败: " + err.Error()})
return
}
c.JSON(200, model.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, model.Response{Code: 500, Message: err.Error()})
return
}
if err := client.SuspendCronJob(ns, c.Param("name"), req.Suspend); err != nil {
c.JSON(500, model.Response{Code: 500, Message: "操作失败: " + err.Error()})
return
}
action := "恢复"
if req.Suspend {
action = "暂停"
}
c.JSON(200, model.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, model.Response{Code: 500, Message: err.Error()})
return
}
if err := client.TriggerCronJob(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) 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, model.Response{Code: 500, Message: err.Error()})
return
}
if err := client.DeleteCronJob(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) ListHPAsAllNS(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
}
hpas, err := client.ListHPAs()
if err != nil {
c.JSON(500, model.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, model.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