511 lines
16 KiB
Go
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
|