refactor(k8s): Q2 kubernetes.go 模块化拆分 — 4204行单文件→12个域文件
将 handlers/kubernetes.go (4204行/134函数) 按业务域拆分为: - kubernetes.go (106L): struct + constructor + 共享helpers - k8s_cluster.go (642L): 集群CRUD + 同步 + RBAC - k8s_workload.go (696L): Deployment/STS/DS/ReplicaSet操作 - k8s_pod.go (653L): Pod日志/终端/exec/指标 - k8s_pod_files.go (206L): Pod文件浏览/上传/下载 - k8s_resource.go (351L): Service/Ingress/ConfigMap/Secret/PVC/Topology - k8s_hpa_cronjob.go (384L): HPA + CronJob - k8s_yaml.go (213L): YAML get/update/delete/apply - k8s_namespace.go (305L): Namespace CRUD - k8s_events.go (207L): 事件列表+聚合 - k8s_node.go (428L): Node详情+资源分配 - k8s_compare.go (146L): 多集群对比 验收: go build零错误, 425条路由不变, 26个K8s API端点回归测试全通过
This commit is contained in:
1 parent
aaab923f4e
commit
4dd5e10b46
15 files changed
+4259
-4122
No files matched your search
@@ -0,0 +1,642 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"ezops/internal/models"
|
||||
k8sclient "ezops/pkg/kubernetesclient"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (h *KubernetesHandler) ListClusters(c *gin.Context) {
|
||||
var query models.PageQuery
|
||||
c.ShouldBindQuery(&query)
|
||||
|
||||
var total int64
|
||||
h.db.Model(&models.KubernetesCluster{}).Count(&total)
|
||||
|
||||
var clusters []models.KubernetesCluster
|
||||
h.db.Offset(query.GetOffset()).Limit(query.GetLimit()).Find(&clusters)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: models.ListResponse{
|
||||
List: clusters,
|
||||
Total: total,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetCluster 获取集群详情
|
||||
// @Summary 获取集群详情
|
||||
// @Description 获取指定K8s集群的详细信息
|
||||
// @Tags K8s集群
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "集群ID"
|
||||
// @Success 200 {object} models.Response{data=models.KubernetesCluster}
|
||||
// @Router /kubernetes-clusters/{id} [get]
|
||||
func (h *KubernetesHandler) GetCluster(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var cluster models.KubernetesCluster
|
||||
if err := h.db.First(&cluster, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "集群不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: cluster,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateCluster 创建集群
|
||||
// @Summary 创建集群
|
||||
// @Description 创建新K8s集群
|
||||
// @Tags K8s集群
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body models.CreateKubernetesClusterRequest true "创建请求"
|
||||
// @Success 200 {object} models.Response{data=models.KubernetesCluster}
|
||||
// @Router /kubernetes-clusters [post]
|
||||
func (h *KubernetesHandler) CreateCluster(c *gin.Context) {
|
||||
var req models.CreateKubernetesClusterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
cluster := models.KubernetesCluster{
|
||||
Name: req.Name,
|
||||
Code: req.Code,
|
||||
Version: req.Version,
|
||||
ConfigType: req.ConfigType,
|
||||
APIServer: req.APIServer,
|
||||
APIEndpoint: req.APIServer, // auto-sync
|
||||
Token: req.Token,
|
||||
KubeConfig: req.KubeConfig,
|
||||
Description: req.Description,
|
||||
Status: "healthy",
|
||||
}
|
||||
|
||||
if err := h.db.Create(&cluster).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "创建失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "创建成功",
|
||||
Data: cluster,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateCluster 更新集群
|
||||
// @Summary 更新集群
|
||||
// @Description 更新指定K8s集群信息
|
||||
// @Tags K8s集群
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "集群ID"
|
||||
// @Param request body models.CreateKubernetesClusterRequest true "更新请求"
|
||||
// @Success 200 {object} models.Response{data=models.KubernetesCluster}
|
||||
// @Router /kubernetes-clusters/{id} [put]
|
||||
func (h *KubernetesHandler) UpdateCluster(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
// 用 map 接收,避免 binding:"required" 校验 // Logan.Li
|
||||
var req map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var cluster models.KubernetesCluster
|
||||
if err := h.db.First(&cluster, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "集群不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 逐字段更新:空值不覆盖原值(防止凭据被清空) // Logan.Li
|
||||
updates := map[string]interface{}{}
|
||||
if v, ok := req["name"]; ok && v != nil && v != "" {
|
||||
updates["name"] = v
|
||||
}
|
||||
if v, ok := req["version"]; ok {
|
||||
updates["version"] = v
|
||||
}
|
||||
if v, ok := req["description"]; ok {
|
||||
updates["description"] = v
|
||||
}
|
||||
if v, ok := req["config_type"]; ok {
|
||||
updates["config_type"] = v
|
||||
}
|
||||
if v, ok := req["api_server"]; ok && v != nil && v != "" {
|
||||
updates["api_server"] = v
|
||||
updates["api_endpoint"] = v
|
||||
}
|
||||
// Token / KubeConfig:空值不覆盖(前端编辑时可能拿不到原值)
|
||||
if v, ok := req["token"]; ok && v != nil && v != "" {
|
||||
updates["token"] = v
|
||||
}
|
||||
if v, ok := req["kube_config"]; ok && v != nil && v != "" {
|
||||
updates["kube_config"] = v
|
||||
}
|
||||
|
||||
if len(updates) == 0 {
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "无更新内容",
|
||||
Data: cluster,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.Model(&cluster).Updates(updates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.db.First(&cluster, id)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "更新成功",
|
||||
Data: cluster,
|
||||
})
|
||||
}
|
||||
|
||||
// GetClusterDetail 返回集群完整信息(含 token/kube_config,供编辑回填) // Logan.Li
|
||||
func (h *KubernetesHandler) GetClusterDetail(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var cluster models.KubernetesCluster
|
||||
if err := h.db.First(&cluster, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "集群不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 返回时手动脱敏:token/kube_config 只返回是否存在标记,不返回原文
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: map[string]interface{}{
|
||||
"id": cluster.ID,
|
||||
"name": cluster.Name,
|
||||
"code": cluster.Code,
|
||||
"version": cluster.Version,
|
||||
"status": cluster.Status,
|
||||
"config_type": cluster.ConfigType,
|
||||
"api_server": cluster.APIServer,
|
||||
"api_endpoint": cluster.APIEndpoint,
|
||||
"description": cluster.Description,
|
||||
"has_token": cluster.Token != "",
|
||||
"has_kube_config": cluster.KubeConfig != "",
|
||||
"node_count": cluster.NodeCount,
|
||||
"pod_count": cluster.PodCount,
|
||||
"service_count": cluster.ServiceCount,
|
||||
"deployment_count": cluster.DeploymentCount,
|
||||
"cpu_usage": cluster.CPUUsage,
|
||||
"memory_usage": cluster.MemoryUsage,
|
||||
"labels": cluster.Labels,
|
||||
"last_sync_at": cluster.LastSyncAt,
|
||||
"created_at": cluster.CreatedAt,
|
||||
"updated_at": cluster.UpdatedAt,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteCluster 删除集群
|
||||
// @Summary 删除集群
|
||||
// @Description 删除指定K8s集群
|
||||
// @Tags K8s集群
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "集群ID"
|
||||
// @Success 200 {object} models.Response
|
||||
// @Router /kubernetes-clusters/{id} [delete]
|
||||
func (h *KubernetesHandler) DeleteCluster(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
if err := h.db.Delete(&models.KubernetesCluster{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "删除失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
// ListNodes 获取节点列表
|
||||
// @Summary 获取节点列表
|
||||
// @Description 获取指定集群的节点列表
|
||||
// @Tags K8s集群
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "集群ID"
|
||||
// @Success 200 {object} models.Response{data=[]models.KubernetesNode}
|
||||
// @Router /kubernetes-clusters/{id}/nodes [get]
|
||||
func (h *KubernetesHandler) SyncCluster(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var cluster models.KubernetesCluster
|
||||
if err := h.db.First(&cluster, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "集群不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
if cluster.KubeConfig == "" && cluster.Token == "" {
|
||||
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "集群未配置凭证"})
|
||||
return
|
||||
}
|
||||
|
||||
client, err := k8sclient.NewClient(cluster.KubeConfig)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "连接K8s失败: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
result := k8sclient.SyncResult{}
|
||||
|
||||
// Fetch node metrics (best-effort — metrics-server may not be installed) // Logan.Li
|
||||
nodeMetrics := map[string]k8sclient.NodeMetricsItem{}
|
||||
if m, err := client.GetNodeMetrics(); err == nil {
|
||||
for _, item := range m.Items {
|
||||
nodeMetrics[item.Metadata.Name] = item
|
||||
}
|
||||
} else {
|
||||
log.Printf("[K8s] sync: GetNodeMetrics failed (metrics-server not installed?): %v", err)
|
||||
}
|
||||
|
||||
// Fetch pod metrics (best-effort) // Logan.Li
|
||||
podMetrics := map[string]k8sclient.PodMetricsItem{} // key: "namespace/podname"
|
||||
if pm, err := client.ListPodMetrics(""); err == nil {
|
||||
for _, item := range pm.Items {
|
||||
podMetrics[item.Metadata.Namespace+"/"+item.Metadata.Name] = item
|
||||
}
|
||||
}
|
||||
|
||||
// Sync nodes
|
||||
if nodes, err := client.ListNodes(); err == nil {
|
||||
// Use raw SQL for reliability (avoid GORM soft-delete / auto-timestamp issues)
|
||||
h.db.Exec("DELETE FROM kubernetes_nodes WHERE cluster_id = ?", cluster.ID)
|
||||
for _, n := range nodes.Items {
|
||||
role := "worker"
|
||||
if _, ok := n.Metadata.Labels["node-role.kubernetes.io/master"]; ok {
|
||||
role = "master"
|
||||
} else if _, ok := n.Metadata.Labels["node-role.kubernetes.io/control-plane"]; ok {
|
||||
role = "master"
|
||||
}
|
||||
nodeStatus := "NotReady"
|
||||
for _, cond := range n.Status.Conditions {
|
||||
if cond.Type == "Ready" && cond.Status == "True" {
|
||||
nodeStatus = "Ready"
|
||||
break
|
||||
}
|
||||
}
|
||||
ip := ""
|
||||
for _, addr := range n.Status.Addresses {
|
||||
if addr.Type == "InternalIP" {
|
||||
ip = addr.Address
|
||||
break
|
||||
}
|
||||
}
|
||||
labelsJSON, _ := json.Marshal(n.Metadata.Labels)
|
||||
now := time.Now()
|
||||
// Compute CPU/memory usage percentage from metrics-server data // Logan.Li
|
||||
cpuUsagePct, memUsagePct := 0.0, 0.0
|
||||
if m, ok := nodeMetrics[n.Metadata.Name]; ok {
|
||||
cpuCores := parseIntResource(n.Status.Capacity["cpu"])
|
||||
memMB := parseMemResource(n.Status.Capacity["memory"])
|
||||
if cpuCores > 0 {
|
||||
cpuMillicores := parseCpuNanoToMilli(m.Usage["cpu"])
|
||||
cpuUsagePct = float64(cpuMillicores) / float64(cpuCores*1000) * 100
|
||||
}
|
||||
if memMB > 0 {
|
||||
memMi := parseMemKiToMi(m.Usage["memory"])
|
||||
memUsagePct = float64(memMi) / float64(memMB) * 100
|
||||
}
|
||||
}
|
||||
err2 := h.db.Exec(`INSERT INTO kubernetes_nodes (cluster_id, name, ip, role, status, os, kubelet_version, cpu, memory, max_pods, cpu_usage, memory_usage, labels, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
cluster.ID, n.Metadata.Name, ip, role, nodeStatus, n.Status.NodeInfo.OSImage, n.Status.NodeInfo.KubeletVersion,
|
||||
parseIntResource(n.Status.Capacity["cpu"]), parseMemResource(n.Status.Capacity["memory"]), parseIntResource(n.Status.Capacity["pods"]),
|
||||
cpuUsagePct, memUsagePct, string(labelsJSON), now, now).Error
|
||||
if err2 != nil {
|
||||
log.Printf("[K8s] insert node %s error: %v", n.Metadata.Name, err2)
|
||||
} else {
|
||||
result.Nodes++
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Printf("[K8s] sync nodes error: %v", err)
|
||||
}
|
||||
|
||||
// Sync pods
|
||||
if pods, err := client.ListPods(); err == nil {
|
||||
h.db.Where("cluster_id = ?", cluster.ID).Delete(&models.KubernetesPod{})
|
||||
for _, p := range pods.Items {
|
||||
image := ""
|
||||
if len(p.Spec.Containers) > 0 {
|
||||
image = p.Spec.Containers[0].Image
|
||||
}
|
||||
restarts := 0
|
||||
if len(p.Status.ContainerStatuses) > 0 {
|
||||
restarts = p.Status.ContainerStatuses[0].RestartCount
|
||||
}
|
||||
labelsJSON, _ := json.Marshal(p.Metadata.Labels)
|
||||
h.db.Create(&models.KubernetesPod{
|
||||
ClusterID: cluster.ID,
|
||||
Namespace: p.Metadata.Namespace,
|
||||
Name: p.Metadata.Name,
|
||||
Status: p.Status.Phase,
|
||||
NodeName: p.Spec.NodeName,
|
||||
IP: p.Status.PodIP,
|
||||
Image: image,
|
||||
RestartCount: restarts,
|
||||
Labels: string(labelsJSON),
|
||||
})
|
||||
result.Pods++
|
||||
}
|
||||
} else {
|
||||
log.Printf("[K8s] sync pods error: %v", err)
|
||||
}
|
||||
|
||||
// Sync deployments
|
||||
if deps, err := client.ListDeployments(); err == nil {
|
||||
h.db.Where("cluster_id = ?", cluster.ID).Delete(&models.KubernetesDeployment{})
|
||||
for _, d := range deps.Items {
|
||||
image := ""
|
||||
if len(d.Spec.Template.Spec.Containers) > 0 {
|
||||
image = d.Spec.Template.Spec.Containers[0].Image
|
||||
}
|
||||
labelsJSON, _ := json.Marshal(d.Metadata.Labels)
|
||||
h.db.Create(&models.KubernetesDeployment{
|
||||
ClusterID: cluster.ID,
|
||||
Namespace: d.Metadata.Namespace,
|
||||
Name: d.Metadata.Name,
|
||||
Replicas: d.Spec.Replicas,
|
||||
AvailableReplicas: d.Status.AvailableReplicas,
|
||||
ReadyReplicas: d.Status.ReadyReplicas,
|
||||
Image: image,
|
||||
Labels: string(labelsJSON),
|
||||
})
|
||||
result.Deployments++
|
||||
}
|
||||
} else {
|
||||
log.Printf("[K8s] sync deps error: %v", err)
|
||||
}
|
||||
|
||||
// Sync services
|
||||
if svcs, err := client.ListServices(); err == nil {
|
||||
h.db.Where("cluster_id = ?", cluster.ID).Delete(&models.KubernetesService{})
|
||||
for _, s := range svcs.Items {
|
||||
portsJSON, _ := json.Marshal(s.Spec.Ports)
|
||||
selectorJSON, _ := json.Marshal(s.Spec.Selector)
|
||||
labelsJSON, _ := json.Marshal(s.Metadata.Labels)
|
||||
h.db.Create(&models.KubernetesService{
|
||||
ClusterID: cluster.ID,
|
||||
Namespace: s.Metadata.Namespace,
|
||||
Name: s.Metadata.Name,
|
||||
Type: s.Spec.Type,
|
||||
ClusterIP: s.Spec.ClusterIP,
|
||||
Ports: string(portsJSON),
|
||||
Selector: string(selectorJSON),
|
||||
Labels: string(labelsJSON),
|
||||
})
|
||||
result.Services++
|
||||
}
|
||||
}
|
||||
|
||||
// Sync ingresses
|
||||
if ings, err := client.ListIngresses(); err == nil {
|
||||
h.db.Where("cluster_id = ?", cluster.ID).Delete(&models.KubernetesIngress{})
|
||||
for _, ing := range ings.Items {
|
||||
hosts := []string{}
|
||||
for _, rule := range ing.Spec.Rules {
|
||||
hosts = append(hosts, rule.Host)
|
||||
}
|
||||
hostsJSON, _ := json.Marshal(hosts)
|
||||
tls := len(ing.Spec.TLS) > 0
|
||||
tlsHosts := []string{}
|
||||
if tls {
|
||||
for _, t := range ing.Spec.TLS {
|
||||
tlsHosts = append(tlsHosts, t.Hosts...)
|
||||
}
|
||||
}
|
||||
tlsHostsJSON, _ := json.Marshal(tlsHosts)
|
||||
labelsJSON, _ := json.Marshal(ing.Metadata.Labels)
|
||||
h.db.Create(&models.KubernetesIngress{
|
||||
ClusterID: cluster.ID,
|
||||
Namespace: ing.Metadata.Namespace,
|
||||
Name: ing.Metadata.Name,
|
||||
Hosts: string(hostsJSON),
|
||||
TLS: tls,
|
||||
TLSHosts: string(tlsHostsJSON),
|
||||
Labels: string(labelsJSON),
|
||||
})
|
||||
result.Ingresses++
|
||||
}
|
||||
}
|
||||
|
||||
// Update cluster stats
|
||||
now := time.Now()
|
||||
h.db.Model(&cluster).Updates(map[string]interface{}{
|
||||
"node_count": result.Nodes,
|
||||
"pod_count": result.Pods,
|
||||
"deployment_count": result.Deployments,
|
||||
"service_count": result.Services,
|
||||
"last_sync_at": &now,
|
||||
})
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: fmt.Sprintf("同步完成: %d nodes, %d pods, %d deployments, %d services, %d ingresses",
|
||||
result.Nodes, result.Pods, result.Deployments, result.Services, result.Ingresses),
|
||||
Data: result,
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Pod Logs
|
||||
// ============================================================================
|
||||
|
||||
// GetPodLogs 获取 Pod 日志(支持 stream=true 实时流)
|
||||
func (h *KubernetesHandler) ListServiceAccountsNS(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
|
||||
}
|
||||
sas, err := client.ListServiceAccounts(c.Param("namespace"))
|
||||
if err != nil {
|
||||
c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()})
|
||||
return
|
||||
}
|
||||
type SASummary struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Name string `json:"name"`
|
||||
SecretCount int `json:"secret_count"`
|
||||
ImagePullSecrets []string `json:"image_pull_secrets"`
|
||||
Age string `json:"age"`
|
||||
}
|
||||
result := make([]SASummary, 0, len(sas))
|
||||
for _, sa := range sas {
|
||||
ipsNames := make([]string, 0, len(sa.ImagePullSecrets))
|
||||
for _, ips := range sa.ImagePullSecrets {
|
||||
ipsNames = append(ipsNames, ips.Name)
|
||||
}
|
||||
result = append(result, SASummary{
|
||||
Namespace: sa.Metadata.Namespace,
|
||||
Name: sa.Metadata.Name,
|
||||
SecretCount: len(sa.Secrets),
|
||||
ImagePullSecrets: ipsNames,
|
||||
Age: formatAge(sa.Metadata.CreationTimestamp),
|
||||
})
|
||||
}
|
||||
c.JSON(200, models.Response{Code: 200, Data: result})
|
||||
}
|
||||
|
||||
// ListRolesNS lists Roles in a namespace
|
||||
func (h *KubernetesHandler) ListRolesNS(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
|
||||
}
|
||||
roles, err := client.ListRoles(c.Param("namespace"))
|
||||
if err != nil {
|
||||
c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()})
|
||||
return
|
||||
}
|
||||
type RoleSummary struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Name string `json:"name"`
|
||||
RuleCount int `json:"rule_count"`
|
||||
Verbs []string `json:"verbs"`
|
||||
Resources []string `json:"resources"`
|
||||
Age string `json:"age"`
|
||||
}
|
||||
result := make([]RoleSummary, 0, len(roles.Items))
|
||||
for _, r := range roles.Items {
|
||||
allVerbs := []string{}
|
||||
allResources := []string{}
|
||||
seen := map[string]bool{}
|
||||
for _, rule := range r.Rules {
|
||||
for _, v := range rule.Verbs {
|
||||
if !seen[v] {
|
||||
allVerbs = append(allVerbs, v)
|
||||
seen[v] = true
|
||||
}
|
||||
}
|
||||
for _, res := range rule.Resources {
|
||||
if !seen[res] {
|
||||
allResources = append(allResources, res)
|
||||
seen[res] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
result = append(result, RoleSummary{
|
||||
Namespace: r.Metadata.Namespace,
|
||||
Name: r.Metadata.Name,
|
||||
RuleCount: len(r.Rules),
|
||||
Verbs: allVerbs,
|
||||
Resources: allResources,
|
||||
Age: formatAge(r.Metadata.CreationTimestamp),
|
||||
})
|
||||
}
|
||||
c.JSON(200, models.Response{Code: 200, Data: result})
|
||||
}
|
||||
|
||||
// ListRoleBindingsNS lists RoleBindings in a namespace
|
||||
func (h *KubernetesHandler) ListRoleBindingsNS(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
|
||||
}
|
||||
rbs, err := client.ListRoleBindings(c.Param("namespace"))
|
||||
if err != nil {
|
||||
c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()})
|
||||
return
|
||||
}
|
||||
type RBSummary struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Name string `json:"name"`
|
||||
RoleRefKind string `json:"role_ref_kind"`
|
||||
RoleRefName string `json:"role_ref_name"`
|
||||
Subjects []string `json:"subjects"`
|
||||
Age string `json:"age"`
|
||||
}
|
||||
result := make([]RBSummary, 0, len(rbs.Items))
|
||||
for _, rb := range rbs.Items {
|
||||
subjStrs := make([]string, 0, len(rb.Subjects))
|
||||
for _, s := range rb.Subjects {
|
||||
if s.Namespace != "" && s.Namespace != rb.Metadata.Namespace {
|
||||
subjStrs = append(subjStrs, fmt.Sprintf("%s/%s (%s)", s.Kind, s.Name, s.Namespace))
|
||||
} else {
|
||||
subjStrs = append(subjStrs, fmt.Sprintf("%s/%s", s.Kind, s.Name))
|
||||
}
|
||||
}
|
||||
result = append(result, RBSummary{
|
||||
Namespace: rb.Metadata.Namespace,
|
||||
Name: rb.Metadata.Name,
|
||||
RoleRefKind: rb.RoleRef.Kind,
|
||||
RoleRefName: rb.RoleRef.Name,
|
||||
Subjects: subjStrs,
|
||||
Age: formatAge(rb.Metadata.CreationTimestamp),
|
||||
})
|
||||
}
|
||||
c.JSON(200, models.Response{Code: 200, Data: result})
|
||||
}
|
||||
Reference in new issue
Block a user