Skip to main content
Glama
container.go32.1 kB
package dynamic import ( "fmt" "strings" "github.com/gin-gonic/gin" "github.com/weibaohui/k8m/pkg/comm/utils" "github.com/weibaohui/k8m/pkg/comm/utils/amis" "github.com/weibaohui/kom/kom" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" ) type ContainerController struct{} func RegisterContainerRoutes(api *gin.RouterGroup) { ctrl := &ContainerController{} api.GET("/:kind/group/:group/version/:version/container_info/ns/:ns/name/:name/container/:container_name", ctrl.ContainerInfo) api.GET("/:kind/group/:group/version/:version/container_resources_info/ns/:ns/name/:name/container/:container_name", ctrl.ContainerResourcesInfo) api.GET("/:kind/group/:group/version/:version/image_pull_secrets/ns/:ns/name/:name", ctrl.ImagePullSecretOptionList) api.GET("/:kind/group/:group/version/:version/container_health_checks/ns/:ns/name/:name/container/:container_name", ctrl.ContainerHealthChecksInfo) api.GET("/:kind/group/:group/version/:version/container_env/ns/:ns/name/:name/container/:container_name", ctrl.ContainerEnvInfo) api.POST("/:kind/group/:group/version/:version/update_image/ns/:ns/name/:name", ctrl.UpdateImageTag) api.POST("/:kind/group/:group/version/:version/update_resources/ns/:ns/name/:name", ctrl.UpdateResources) api.POST("/:kind/group/:group/version/:version/update_health_checks/ns/:ns/name/:name", ctrl.UpdateHealthChecks) api.POST("/:kind/group/:group/version/:version/update_env/ns/:ns/name/:name", ctrl.UpdateContainerEnv) } // @Summary 获取容器镜像拉取密钥选项 // @Security BearerAuth // @Param cluster query string true "集群名称" // @Param kind path string true "资源类型" // @Param group path string true "API组" // @Param version path string true "API版本" // @Param ns path string true "命名空间" // @Param name path string true "资源名称" // @Success 200 {object} string // @Router /k8s/cluster/{cluster}/{kind}/group/{group}/version/{version}/image_pull_secrets/ns/{ns}/name/{name} [get] func (cc *ContainerController) ImagePullSecretOptionList(c *gin.Context) { name := c.Param("name") ns := c.Param("ns") group := c.Param("group") kind := c.Param("kind") version := c.Param("version") ctx := amis.GetContextWithUser(c) selectedCluster, err := amis.GetSelectedCluster(c) if err != nil { amis.WriteJsonError(c, err) return } var item *unstructured.Unstructured err = kom.Cluster(selectedCluster).WithContext(ctx). CRD(group, version, kind). Namespace(ns). Name(name).Get(&item).Error if err != nil { amis.WriteJsonData(c, gin.H{ "options": make([]map[string]string, 0), }) return } imagePullSecrets, _ := cc.getImagePullSecrets(item) if len(imagePullSecrets) == 0 { amis.WriteJsonData(c, gin.H{ "options": make([]map[string]string, 0), }) return } // 从Secret中寻找镜像拉取密钥 // 获取list var secretsList []*v1.Secret err = kom.Cluster(selectedCluster).WithContext(ctx). Resource(&v1.Secret{}). Namespace(ns). Where(fmt.Sprintf("type = '%s' or type = '%s' ", v1.SecretTypeDockerConfigJson, v1.SecretTypeDockercfg)). List(&secretsList).Error if err != nil { amis.WriteJsonData(c, gin.H{ "options": make([]map[string]string, 0), }) return } if len(secretsList) == 0 { amis.WriteJsonData(c, gin.H{ "options": make([]map[string]string, 0), }) return } var options []map[string]string for _, s := range secretsList { options = append(options, map[string]string{ "label": s.Name, "value": s.Name, }) } amis.WriteJsonData(c, gin.H{ "options": options, "value": strings.Join(imagePullSecrets, ","), }) } // @Summary 获取容器资源信息 // @Security BearerAuth // @Param cluster query string true "集群名称" // @Param kind path string true "资源类型" // @Param group path string true "API组" // @Param version path string true "API版本" // @Param ns path string true "命名空间" // @Param name path string true "资源名称" // @Param container_name path string true "容器名称" // @Success 200 {object} string // @Router /k8s/cluster/{cluster}/{kind}/group/{group}/version/{version}/container_resources_info/ns/{ns}/name/{name}/container/{container_name} [get] func (cc *ContainerController) ContainerResourcesInfo(c *gin.Context) { name := c.Param("name") ns := c.Param("ns") group := c.Param("group") kind := c.Param("kind") version := c.Param("version") containerName := c.Param("container_name") ctx := amis.GetContextWithUser(c) selectedCluster, err := amis.GetSelectedCluster(c) if err != nil { amis.WriteJsonError(c, err) return } var item *unstructured.Unstructured err = kom.Cluster(selectedCluster).WithContext(ctx). CRD(group, version, kind). Namespace(ns). Name(name).Get(&item).Error if err != nil { amis.WriteJsonError(c, err) return } requestCPU, limitCPU, requestMemory, limitMemory, err := cc.getContainerResourcesInfoByName(item, containerName) if err != nil { amis.WriteJsonError(c, err) return } amis.WriteJsonData(c, gin.H{ "name": containerName, "request_cpu": requestCPU, "limit_cpu": limitCPU, "request_memory": requestMemory, "limit_memory": limitMemory, }) } func (cc *ContainerController) getContainerResourcesInfoByName(item *unstructured.Unstructured, containerName string) (string, string, string, string, error) { // 获取资源类型 kind := item.GetKind() // 根据资源类型获取 containers 的路径 resourcePaths, err := getResourcePaths(kind) if err != nil { return "", "", "", "", err } containersPath := append(resourcePaths, "containers") // 获取嵌套字段 containers, found, err := unstructured.NestedSlice(item.Object, containersPath...) if err != nil { return "", "", "", "", fmt.Errorf("error getting containers: %w", err) } if !found { return "", "", "", "", fmt.Errorf("containers field not found") } // 遍历 containers 列表 for _, container := range containers { // 断言 container 类型为 map[string]any containerMap, ok := container.(map[string]any) if !ok { return "", "", "", "", fmt.Errorf("unexpected container format") } // 获取容器的 name name, _, err := unstructured.NestedString(containerMap, "name") if err != nil { return "", "", "", "", fmt.Errorf("error getting container name: %w", err) } // 如果 name 匹配目标容器名,则获取其 image if name == containerName { // 获取 resources resourcesMap, found, err := unstructured.NestedMap(containerMap, "resources") if err != nil { return "", "", "", "", fmt.Errorf("error getting container resources: %w", err) } if !found { return "", "", "", "", nil // 如果没有 resources 字段,返回空字符串 } // 获取 requests requestsMap, found, err := unstructured.NestedMap(resourcesMap, "requests") if err != nil { return "", "", "", "", fmt.Errorf("error getting container requests: %w", err) } if !found { requestsMap = make(map[string]any) // 如果没有 requests 字段,初始化为空 map } // 获取 limits limitsMap, found, err := unstructured.NestedMap(resourcesMap, "limits") if err != nil { return "", "", "", "", fmt.Errorf("error getting container limits: %w", err) } if !found { limitsMap = make(map[string]any) // 如果没有 limits 字段,初始化为空 map } // 获取 request CPU requestCPU, _, err := unstructured.NestedString(requestsMap, "cpu") if err != nil { return "", "", "", "", fmt.Errorf("error getting container request cpu: %w", err) } // 获取 request 内存 requestMemory, _, err := unstructured.NestedString(requestsMap, "memory") if err != nil { return "", "", "", "", fmt.Errorf("error getting container request memory: %w", err) } // 获取 limit CPU limitCPU, _, err := unstructured.NestedString(limitsMap, "cpu") if err != nil { return "", "", "", "", fmt.Errorf("error getting container limit cpu: %w", err) } // 获取 limit 内存 limitMemory, _, err := unstructured.NestedString(limitsMap, "memory") if err != nil { return "", "", "", "", fmt.Errorf("error getting container limit memory: %w", err) } return requestCPU, limitCPU, requestMemory, limitMemory, nil } } // 如果未找到匹配的容器名 return "", "", "", "", fmt.Errorf("container with name %q not found", containerName) } // 资源信息结构体 // json // {"container_name":"my-container","request_cpu":"1","request_memory":"1","request_memory":"1Gi","limit_memory":"1Gi"} type resourceInfo struct { ContainerName string `json:"container_name"` RequestCpu string `json:"request_cpu"` LimitCpu string `json:"limit_cpu"` RequestMemory string `json:"request_memory"` LimitMemory string `json:"limit_memory"` } // @Summary 更新容器资源配置 // @Security BearerAuth // @Param cluster query string true "集群名称" // @Param kind path string true "资源类型" // @Param group path string true "API组" // @Param version path string true "API版本" // @Param ns path string true "命名空间" // @Param name path string true "资源名称" // @Param body body resourceInfo true "资源配置信息" // @Success 200 {object} string // @Router /k8s/cluster/{cluster}/{kind}/group/{group}/version/{version}/update_resources/ns/{ns}/name/{name} [post] func (cc *ContainerController) UpdateResources(c *gin.Context) { name := c.Param("name") ns := c.Param("ns") group := c.Param("group") kind := c.Param("kind") version := c.Param("version") ctx := amis.GetContextWithUser(c) selectedCluster, err := amis.GetSelectedCluster(c) if err != nil { amis.WriteJsonError(c, err) return } var info resourceInfo if err = c.ShouldBindJSON(&info); err != nil { amis.WriteJsonError(c, err) return } patchData, err := generateResourcePatch(kind, info) if err != nil { amis.WriteJsonError(c, err) return } patchJSON := utils.ToJSON(patchData) var item any err = kom.Cluster(selectedCluster). WithContext(ctx). CRD(group, version, kind). Namespace(ns).Name(name). Patch(&item, types.StrategicMergePatchType, patchJSON).Error amis.WriteJsonErrorOrOK(c, err) } // 生成资源patch数据 func generateResourcePatch(kind string, info resourceInfo) (map[string]any, error) { // 获取资源路径 paths, err := getResourcePaths(kind) if err != nil { return nil, err } // 动态构造 patch 数据 patch := make(map[string]any) current := patch // 按层级动态生成嵌套结构 for _, path := range paths { if _, exists := current[path]; !exists { current[path] = make(map[string]any) } current = current[path].(map[string]any) } // 构造资源请求和限制 resources := make(map[string]any) // 设置请求资源 requests := make(map[string]string) if info.RequestCpu != "" { requests["cpu"] = info.RequestCpu } if info.RequestMemory != "" { requests["memory"] = info.RequestMemory } if len(requests) > 0 { resources["requests"] = requests } // 设置限制资源 limits := make(map[string]string) if info.LimitCpu != "" { limits["cpu"] = info.LimitCpu } if info.LimitMemory != "" { limits["memory"] = info.LimitMemory } if len(limits) > 0 { resources["limits"] = limits } // 构造容器数组 current["containers"] = []map[string]any{ { "name": info.ContainerName, "resources": resources, }, } return patch, nil } // @Summary 获取容器基本信息 // @Security BearerAuth // @Param cluster query string true "集群名称" // @Param kind path string true "资源类型" // @Param group path string true "API组" // @Param version path string true "API版本" // @Param ns path string true "命名空间" // @Param name path string true "资源名称" // @Param container_name path string true "容器名称" // @Success 200 {object} string // @Router /k8s/cluster/{cluster}/{kind}/group/{group}/version/{version}/container_info/ns/{ns}/name/{name}/container/{container_name} [get] func (cc *ContainerController) ContainerInfo(c *gin.Context) { name := c.Param("name") ns := c.Param("ns") group := c.Param("group") kind := c.Param("kind") version := c.Param("version") containerName := c.Param("container_name") ctx := amis.GetContextWithUser(c) selectedCluster, err := amis.GetSelectedCluster(c) if err != nil { amis.WriteJsonError(c, err) return } var item *unstructured.Unstructured err = kom.Cluster(selectedCluster).WithContext(ctx). CRD(group, version, kind). Namespace(ns). Name(name).Get(&item).Error if err != nil { amis.WriteJsonError(c, err) return } imageFullName, imagePullPolicy, err := cc.getContainerImageByName(item, containerName) if err != nil { amis.WriteJsonError(c, err) return } image, tag := utils.GetImageNameAndTag(imageFullName) amis.WriteJsonData(c, gin.H{ "name": containerName, "image": image, "tag": tag, "image_pull_policy": imagePullPolicy, }) } // 获取container的环境变量信息 // @Summary 获取容器环境变量信息 // @Security BearerAuth // @Param cluster query string true "集群名称" // @Param kind path string true "资源类型" // @Param group path string true "API组" // @Param version path string true "API版本" // @Param ns path string true "命名空间" // @Param name path string true "资源名称" // @Param container_name path string true "容器名称" // @Success 200 {object} string // @Router /k8s/cluster/{cluster}/{kind}/group/{group}/version/{version}/container_env/ns/{ns}/name/{name}/container/{container_name} [get] func (cc *ContainerController) ContainerEnvInfo(c *gin.Context) { name := c.Param("name") ns := c.Param("ns") group := c.Param("group") kind := c.Param("kind") version := c.Param("version") containerName := c.Param("container_name") ctx := amis.GetContextWithUser(c) selectedCluster, err := amis.GetSelectedCluster(c) if err != nil { amis.WriteJsonError(c, err) return } var item *unstructured.Unstructured err = kom.Cluster(selectedCluster).WithContext(ctx). CRD(group, version, kind). Namespace(ns). Name(name).Get(&item).Error if err != nil { amis.WriteJsonError(c, err) return } // 返回格式为 envVars, err := cc.getContainerEnvVarsByName(item, containerName) if err != nil { amis.WriteJsonError(c, err) return } amis.WriteJsonData(c, gin.H{ "envs": envVars, }) } type ContainerEnv struct { ContainerName string `json:"container_name"` Envs map[string]string `json:"envs"` } // @Summary 更新容器环境变量 // @Security BearerAuth // @Param cluster query string true "集群名称" // @Param kind path string true "资源类型" // @Param group path string true "API组" // @Param version path string true "API版本" // @Param ns path string true "命名空间" // @Param name path string true "资源名称" // @Param body body ContainerEnv true "容器环境变量信息" // @Success 200 {object} string // @Router /k8s/cluster/{cluster}/{kind}/group/{group}/version/{version}/update_env/ns/{ns}/name/{name} [post] func (cc *ContainerController) UpdateContainerEnv(c *gin.Context) { name := c.Param("name") ns := c.Param("ns") group := c.Param("group") kind := c.Param("kind") version := c.Param("version") ctx := amis.GetContextWithUser(c) selectedCluster, err := amis.GetSelectedCluster(c) if err != nil { amis.WriteJsonError(c, err) return } var info ContainerEnv if err = c.ShouldBindJSON(&info); err != nil { amis.WriteJsonError(c, err) return } patchData, err := cc.generateEnvPatch(kind, info) if err != nil { amis.WriteJsonError(c, err) return } patchJSON := utils.ToJSON(patchData) var patch any err = kom.Cluster(selectedCluster). WithContext(ctx). CRD(group, version, kind). Namespace(ns).Name(name). Patch(&patch, types.StrategicMergePatchType, patchJSON).Error amis.WriteJsonErrorOrOK(c, err) } // 验证环境变量名称是否符合Kubernetes规范 func isValidEnvVarName(name string) bool { if len(name) == 0 { return false } if name[0] >= '0' && name[0] <= '9' { return false } for _, c := range name { if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '.') { return false } } return true } func (cc *ContainerController) generateEnvPatch(kind string, info ContainerEnv) (map[string]any, error) { // 获取资源路径 paths, err := getResourcePaths(kind) if err != nil { return nil, err } // 动态构造 patch 数据 patch := make(map[string]any) // 验证环境变量名称是否符合规范 for key := range info.Envs { if !isValidEnvVarName(key) { return nil, fmt.Errorf("环境变量名'%s'无效: 只能包含字母、数字、_、-或.,且不能以数字开头", key) } } current := patch // 按层级动态生成嵌套结构 for _, path := range paths { if _, exists := current[path]; !exists { current[path] = make(map[string]any) } current = current[path].(map[string]any) } // 构造环境变量数组 envArray := make([]map[string]string, 0) for key, value := range info.Envs { // 验证key是否合法 envArray = append(envArray, map[string]string{ "name": key, "value": value, }) } // 如果没有环境变量,设置envarray为nil if len(envArray) == 0 { envArray = nil } // 构造容器数组 current["containers"] = []map[string]any{ { "name": info.ContainerName, "env": envArray, }, } return patch, nil } // 获取container的env信息 func (cc *ContainerController) getContainerEnvVarsByName(item *unstructured.Unstructured, containerName string) (map[string]string, error) { // 获取资源类型 kind := item.GetKind() // 根据资源类型获取 containers 的路径 resourcePaths, err := getResourcePaths(kind) if err != nil { return nil, err } containersPath := append(resourcePaths, "containers") // 获取嵌套字段 containers, found, err := unstructured.NestedSlice(item.Object, containersPath...) if err != nil { return nil, fmt.Errorf("error getting containers: %w", err) } if !found { return nil, fmt.Errorf("containers field not found") } // 遍历 containers 列表 for _, container := range containers { // 断言 container 类型为 map[string]any containerMap, ok := container.(map[string]any) if !ok { return nil, fmt.Errorf("unexpected container format") } // 获取容器的 name name, _, err := unstructured.NestedString(containerMap, "name") if err != nil { return nil, fmt.Errorf("error getting container name: %w", err) } // 如果 name 匹配目标容器名,则获取其 image if name == containerName { // 获取 env 字段 env, found, err := unstructured.NestedSlice(containerMap, "env") if err != nil { return nil, fmt.Errorf("error getting container env: %w", err) } // 如果未找到 env 字段,则返回空列表 if !found { return make(map[string]string), nil } // 遍历 env 列表 envVars := make(map[string]string) for _, envVar := range env { // 断言 envVar 类型为 map[string]any envVarMap, ok := envVar.(map[string]any) if !ok { return nil, fmt.Errorf("unexpected envVar format") } // 获取 envVar 的 name 和 value name, _, err := unstructured.NestedString(envVarMap, "name") if err != nil { return nil, fmt.Errorf("error getting envVar name: %w", err) } value, _, err := unstructured.NestedString(envVarMap, "value") if err != nil { return nil, fmt.Errorf("error getting envVar value: %w", err) } // 将 name 和 value 组合成 map 并添加到结果列表中 envVars[name] = value } return envVars, nil } } // 如果未找到匹配的容器名 return nil, fmt.Errorf("container with name %q not found", containerName) } // 获取 imagePullSecrets 列表 func (cc *ContainerController) getImagePullSecrets(item *unstructured.Unstructured) ([]string, error) { // 获取资源类型 kind := item.GetKind() // 根据资源类型获取 imagePullSecrets 的路径 resourcePaths, err := getResourcePaths(kind) if err != nil { return nil, err } imagePullSecretsPath := append(resourcePaths, "imagePullSecrets") // 获取嵌套字段 secrets, found, err := unstructured.NestedSlice(item.Object, imagePullSecretsPath...) if err != nil { return nil, fmt.Errorf("error getting imagePullSecrets: %w", err) } if !found { return nil, fmt.Errorf("imagePullSecrets field not found for kind %q", kind) } // 提取 secret name 列表 var secretNames []string for _, secret := range secrets { secretMap, ok := secret.(map[string]any) if !ok { return nil, fmt.Errorf("unexpected imagePullSecret format") } name, found, err := unstructured.NestedString(secretMap, "name") if err != nil { return nil, fmt.Errorf("error getting imagePullSecret name: %w", err) } if found { secretNames = append(secretNames, name) } } return secretNames, nil } func (cc *ContainerController) getContainerImageByName(item *unstructured.Unstructured, containerName string) (string, string, error) { // 获取资源类型 kind := item.GetKind() // 根据资源类型获取 containers 的路径 resourcePaths, err := getResourcePaths(kind) if err != nil { return "", "", err } containersPath := append(resourcePaths, "containers") // 获取嵌套字段 containers, found, err := unstructured.NestedSlice(item.Object, containersPath...) if err != nil { return "", "", fmt.Errorf("error getting containers: %w", err) } if !found { return "", "", fmt.Errorf("containers field not found") } // 遍历 containers 列表 for _, container := range containers { // 断言 container 类型为 map[string]any containerMap, ok := container.(map[string]any) if !ok { return "", "", fmt.Errorf("unexpected container format") } // 获取容器的 name name, _, err := unstructured.NestedString(containerMap, "name") if err != nil { return "", "", fmt.Errorf("error getting container name: %w", err) } // 如果 name 匹配目标容器名,则获取其 image if name == containerName { image, _, err := unstructured.NestedString(containerMap, "image") if err != nil { return "", "", fmt.Errorf("error getting container image: %w", err) } imagePullPolicy, _, err := unstructured.NestedString(containerMap, "imagePullPolicy") if err != nil { return "", "", fmt.Errorf("error getting container imagePullPolicy: %w", err) } return image, imagePullPolicy, nil } } // 如果未找到匹配的容器名 return "", "", fmt.Errorf("container with name %q not found", containerName) } // json // {"container_name":"my-container","image":"my-image","name":"my-container","tag":"sss1","image_pull_secrets":"myregistrykey"} type imageInfo struct { ContainerName string `json:"container_name"` Image string `json:"image"` Tag string `json:"tag"` ImagePullSecrets string `json:"image_pull_secrets"` ImagePullPolicy string `json:"image_pull_policy"` } // @Summary 更新容器镜像标签 // @Security BearerAuth // @Param cluster query string true "集群名称" // @Param kind path string true "资源类型" // @Param group path string true "API组" // @Param version path string true "API版本" // @Param ns path string true "命名空间" // @Param name path string true "资源名称" // @Param body body imageInfo true "镜像信息" // @Success 200 {object} string // @Router /k8s/cluster/{cluster}/{kind}/group/{group}/version/{version}/update_image/ns/{ns}/name/{name} [post] func (cc *ContainerController) UpdateImageTag(c *gin.Context) { name := c.Param("name") ns := c.Param("ns") group := c.Param("group") kind := c.Param("kind") version := c.Param("version") ctx := amis.GetContextWithUser(c) selectedCluster, err := amis.GetSelectedCluster(c) if err != nil { amis.WriteJsonError(c, err) return } var info imageInfo if err = c.ShouldBindJSON(&info); err != nil { amis.WriteJsonError(c, err) return } patchData, err := cc.generateDynamicPatch(kind, info) if err != nil { amis.WriteJsonError(c, err) return } patchJSON := utils.ToJSON(patchData) var item any err = kom.Cluster(selectedCluster). WithContext(ctx). CRD(group, version, kind). Namespace(ns).Name(name). Patch(&item, types.StrategicMergePatchType, patchJSON).Error amis.WriteJsonErrorOrOK(c, err) } // 生成动态的 patch 数据 func (cc *ContainerController) generateDynamicPatch(kind string, info imageInfo) (map[string]any, error) { // 获取资源路径 paths, err := getResourcePaths(kind) if err != nil { return nil, err } // 动态构造 patch 数据 patch := make(map[string]any) current := patch // 按层级动态生成嵌套结构 for _, path := range paths { if _, exists := current[path]; !exists { current[path] = make(map[string]any) } current = current[path].(map[string]any) } // 构造 `imagePullSecrets` if info.ImagePullSecrets == "" { current["imagePullSecrets"] = nil // 删除字段 } else { secretNames := strings.Split(info.ImagePullSecrets, ",") imagePullSecrets := make([]map[string]string, 0, len(secretNames)) for _, name := range secretNames { imagePullSecrets = append(imagePullSecrets, map[string]string{"name": name}) } current["imagePullSecrets"] = imagePullSecrets } // 构造 `containers` current["containers"] = []map[string]string{ { "name": info.ContainerName, "image": fmt.Sprintf("%s:%s", info.Image, info.Tag), "imagePullPolicy": info.ImagePullPolicy, }, } return patch, nil } // @Summary 获取容器健康检查信息 // @Security BearerAuth // @Param cluster query string true "集群名称" // @Param kind path string true "资源类型" // @Param group path string true "API组" // @Param version path string true "API版本" // @Param ns path string true "命名空间" // @Param name path string true "资源名称" // @Param container_name path string true "容器名称" // @Success 200 {object} string // @Router /k8s/cluster/{cluster}/{kind}/group/{group}/version/{version}/container_health_checks/ns/{ns}/name/{name}/container/{container_name} [get] func (cc *ContainerController) ContainerHealthChecksInfo(c *gin.Context) { name := c.Param("name") ns := c.Param("ns") group := c.Param("group") kind := c.Param("kind") version := c.Param("version") containerName := c.Param("container_name") ctx := amis.GetContextWithUser(c) selectedCluster, err := amis.GetSelectedCluster(c) if err != nil { amis.WriteJsonError(c, err) return } var item *unstructured.Unstructured err = kom.Cluster(selectedCluster).WithContext(ctx). CRD(group, version, kind). Namespace(ns). Name(name).Get(&item).Error if err != nil { amis.WriteJsonError(c, err) return } healthChecks, err := cc.getContainerHealthChecksByName(item, containerName) if err != nil { amis.WriteJsonError(c, err) return } amis.WriteJsonData(c, gin.H{ "container_name": containerName, "readiness_probe": healthChecks["readinessProbe"], "liveness_probe": healthChecks["livenessProbe"], }) } // 获取容器健康检查信息 func (cc *ContainerController) getContainerHealthChecksByName(item *unstructured.Unstructured, containerName string) (map[string]any, error) { // 获取资源类型 kind := item.GetKind() // 根据资源类型获取 containers 的路径 resourcePaths, err := getResourcePaths(kind) if err != nil { return nil, err } containersPath := append(resourcePaths, "containers") // 获取嵌套字段 containers, found, err := unstructured.NestedSlice(item.Object, containersPath...) if err != nil { return nil, fmt.Errorf("error getting containers: %w", err) } if !found { return nil, fmt.Errorf("containers field not found") } // 遍历 containers 列表 for _, container := range containers { containerMap, ok := container.(map[string]any) if !ok { return nil, fmt.Errorf("unexpected container format") } name, _, err := unstructured.NestedString(containerMap, "name") if err != nil { return nil, fmt.Errorf("error getting container name: %w", err) } if name == containerName { result := make(map[string]any) // 获取就绪检查 if readinessProbe, found, _ := unstructured.NestedMap(containerMap, "readinessProbe"); found { result["readinessProbe"] = readinessProbe } // 获取存活检查 if livenessProbe, found, _ := unstructured.NestedMap(containerMap, "livenessProbe"); found { result["livenessProbe"] = livenessProbe } return result, nil } } return nil, fmt.Errorf("container with name %q not found", containerName) } // 健康检查配置结构体 type HealthCheckInfo struct { ContainerName string `json:"container_name"` LivenessType string `json:"liveness_type"` ReadinessType string `json:"readiness_type"` ReadinessProbe map[string]any `json:"readiness_probe,omitempty"` LivenessProbe map[string]any `json:"liveness_probe,omitempty"` } // @Summary 更新容器健康检查配置 // @Security BearerAuth // @Param cluster query string true "集群名称" // @Param kind path string true "资源类型" // @Param group path string true "API组" // @Param version path string true "API版本" // @Param ns path string true "命名空间" // @Param name path string true "资源名称" // @Param body body HealthCheckInfo true "健康检查配置信息" // @Success 200 {object} string // @Router /k8s/cluster/{cluster}/{kind}/group/{group}/version/{version}/update_health_checks/ns/{ns}/name/{name} [post] func (cc *ContainerController) UpdateHealthChecks(c *gin.Context) { name := c.Param("name") ns := c.Param("ns") group := c.Param("group") kind := c.Param("kind") version := c.Param("version") ctx := amis.GetContextWithUser(c) selectedCluster, err := amis.GetSelectedCluster(c) if err != nil { amis.WriteJsonError(c, err) return } var info HealthCheckInfo if err := c.ShouldBindJSON(&info); err != nil { amis.WriteJsonError(c, err) return } patchData, err := cc.generateHealthCheckPatch(kind, info) klog.V(6).Infof("UpdateHealthChecks Patch JSON :\n%s\n", patchData) if err != nil { amis.WriteJsonError(c, err) return } patchJSON := utils.ToJSON(patchData) var item any err = kom.Cluster(selectedCluster). WithContext(ctx). CRD(group, version, kind). Namespace(ns).Name(name). Patch(&item, types.StrategicMergePatchType, patchJSON).Error // 指定 Patch 类型 amis.WriteJsonErrorOrOK(c, err) } // 生成健康检查的 patch 数据 func (cc *ContainerController) generateHealthCheckPatch(kind string, info HealthCheckInfo) (map[string]any, error) { // 获取资源路径 paths, err := getResourcePaths(kind) if err != nil { return nil, err } // 动态构造 patch 数据 patch := make(map[string]any) current := patch // 按层级动态生成嵌套结构 for _, path := range paths { if _, exists := current[path]; !exists { current[path] = make(map[string]any) } current = current[path].(map[string]any) } // 判断健康检查类型 switch info.LivenessType { case "httpGet": info.LivenessProbe["exec"] = nil info.LivenessProbe["tcpSocket"] = nil case "exec": info.LivenessProbe["httpGet"] = nil info.LivenessProbe["tcpSocket"] = nil case "tcpSocket": info.LivenessProbe["httpGet"] = nil info.LivenessProbe["exec"] = nil default: info.LivenessProbe = nil } switch info.ReadinessType { case "httpGet": info.ReadinessProbe["exec"] = nil info.ReadinessProbe["tcpSocket"] = nil case "exec": info.ReadinessProbe["httpGet"] = nil info.ReadinessProbe["tcpSocket"] = nil case "tcpSocket": info.ReadinessProbe["httpGet"] = nil info.ReadinessProbe["exec"] = nil default: info.ReadinessProbe = nil } // 构造容器数组 current["containers"] = []map[string]any{ { "name": info.ContainerName, "livenessProbe": info.LivenessProbe, "readinessProbe": info.ReadinessProbe, }, } return patch, nil }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/weibaohui/k8m'

If you have feedback or need assistance with the MCP directory API, please join our Discord server