package rest import ( "encoding/json" "net/http" "strings" "github.com/gorilla/mux" "github.com/ocdp/cluster-service/internal/adapter/input/http/dto" "github.com/ocdp/cluster-service/internal/domain/entity" "github.com/ocdp/cluster-service/internal/domain/service" ) // InstanceHandler 实例 Handler type InstanceHandler struct { instanceService *service.InstanceService } // NewInstanceHandler 创建实例 Handler func NewInstanceHandler(instanceService *service.InstanceService) *InstanceHandler { return &InstanceHandler{ instanceService: instanceService, } } // CreateInstance 创建实例 // @Summary 创建实例 // @Description 在指定集群上部署一个 artifact // @Tags Instances // @Accept json // @Produce json // @Security BearerAuth // @Param cluster_id path string true "集群 ID" // @Param request body dto.CreateInstanceRequest true "实例配置" // @Success 201 {object} dto.InstanceResponse // @Failure 400 {object} dto.ErrorResponse // @Router /clusters/{cluster_id}/instances [post] func (h *InstanceHandler) CreateInstance(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) clusterID := vars["cluster_id"] var req dto.CreateInstanceRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondError(w, http.StatusBadRequest, "Invalid request body", err.Error()) return } req.Normalize() if req.Tag == "" { respondError(w, http.StatusBadRequest, "Invalid request", "version/tag is required") return } // Extract chart name from repository (e.g., "charts/nginx" -> "nginx") chart := req.Repository if lastSlash := strings.LastIndex(req.Repository, "/"); lastSlash != -1 { chart = req.Repository[lastSlash+1:] } // 创建实体 instance := entity.NewInstance( "", // workspaceID - will be set based on user "", // ownerID - will be set based on user clusterID, req.RegistryID, "", // chartReferenceID - not used in legacy API "", // valuesTemplateID - not used in legacy API req.Name, req.Namespace, req.Repository, chart, // Extracted chart name req.Tag, // Tag mapped to version ) instance.Description = req.Description if req.Values != nil { instance.SetValues(req.Values) } if req.ValuesYAML != "" { instance.SetValuesYAML(req.ValuesYAML) } // 调用领域服务 if err := h.instanceService.CreateInstance(r.Context(), instance); err != nil { respondError(w, http.StatusBadRequest, "Failed to create instance", err.Error()) return } // 返回响应 response := &dto.InstanceResponse{ ID: instance.ID, ClusterID: instance.ClusterID, Name: instance.Name, Namespace: instance.Namespace, RegistryID: instance.RegistryID, Repository: instance.Repository, Chart: instance.Chart, Version: instance.Version, Description: instance.Description, Status: string(instance.Status), StatusReason: instance.StatusReason, LastOperation: string(instance.LastOperation), LastError: instance.LastError, Revision: instance.Revision, Values: instance.Values, CreatedAt: instance.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), UpdatedAt: instance.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"), } respondJSON(w, http.StatusCreated, response) } // GetInstance 获取实例详情 // @Summary 获取实例详情 // @Tags Instances // @Produce json // @Security BearerAuth // @Param cluster_id path string true "集群 ID" // @Param instance_id path string true "实例 ID" // @Success 200 {object} dto.InstanceResponse // @Failure 404 {object} dto.ErrorResponse // @Router /clusters/{cluster_id}/instances/{instance_id} [get] func (h *InstanceHandler) GetInstance(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) instanceID := vars["instance_id"] instance, err := h.instanceService.GetInstance(r.Context(), instanceID) if err != nil { respondError(w, http.StatusNotFound, "Instance not found", err.Error()) return } response := &dto.InstanceResponse{ ID: instance.ID, ClusterID: instance.ClusterID, Name: instance.Name, Namespace: instance.Namespace, RegistryID: instance.RegistryID, Repository: instance.Repository, Chart: instance.Chart, Version: instance.Version, Description: instance.Description, Status: string(instance.Status), StatusReason: instance.StatusReason, LastOperation: string(instance.LastOperation), LastError: instance.LastError, Revision: instance.Revision, Values: instance.Values, CreatedAt: instance.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), UpdatedAt: instance.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"), } respondJSON(w, http.StatusOK, response) } // ListInstances 列出集群的所有实例 // @Summary 列出实例 // @Tags Instances // @Produce json // @Security BearerAuth // @Param cluster_id path string true "集群 ID" // @Success 200 {object} dto.InstanceListResponse // @Failure 500 {object} dto.ErrorResponse // @Router /clusters/{cluster_id}/instances [get] func (h *InstanceHandler) ListInstances(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) clusterID := vars["cluster_id"] instances, err := h.instanceService.ListInstancesByCluster(r.Context(), clusterID) if err != nil { respondError(w, http.StatusInternalServerError, "Failed to list instances", err.Error()) return } responses := make([]*dto.InstanceResponse, 0, len(instances)) for _, instance := range instances { responses = append(responses, &dto.InstanceResponse{ ID: instance.ID, ClusterID: instance.ClusterID, Name: instance.Name, Namespace: instance.Namespace, RegistryID: instance.RegistryID, Repository: instance.Repository, Chart: instance.Chart, Version: instance.Version, Description: instance.Description, Status: string(instance.Status), StatusReason: instance.StatusReason, LastOperation: string(instance.LastOperation), LastError: instance.LastError, Revision: instance.Revision, CreatedAt: instance.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), UpdatedAt: instance.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"), }) } response := &dto.InstanceListResponse{ Instances: responses, Total: len(responses), } respondJSON(w, http.StatusOK, response) } // UpdateInstance 更新实例 // @Summary 更新实例 // @Tags Instances // @Accept json // @Produce json // @Security BearerAuth // @Param cluster_id path string true "集群 ID" // @Param instance_id path string true "实例 ID" // @Param request body dto.UpdateInstanceRequest true "更新内容" // @Success 200 {object} dto.InstanceResponse // @Failure 404 {object} dto.ErrorResponse // @Router /clusters/{cluster_id}/instances/{instance_id} [put] func (h *InstanceHandler) UpdateInstance(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) instanceID := vars["instance_id"] var req dto.UpdateInstanceRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondError(w, http.StatusBadRequest, "Invalid request body", err.Error()) return } // 获取现有实例 instance, err := h.instanceService.GetInstance(r.Context(), instanceID) if err != nil { respondError(w, http.StatusNotFound, "Instance not found", err.Error()) return } // 更新字段 if req.Version != "" { instance.Upgrade(req.Version, req.Values) } if req.Description != "" { instance.Description = req.Description } if req.ValuesYAML != "" { instance.SetValuesYAML(req.ValuesYAML) } // 调用领域服务 if err := h.instanceService.UpdateInstance(r.Context(), instance); err != nil { respondError(w, http.StatusBadRequest, "Failed to update instance", err.Error()) return } response := &dto.InstanceResponse{ ID: instance.ID, ClusterID: instance.ClusterID, Name: instance.Name, Namespace: instance.Namespace, RegistryID: instance.RegistryID, Repository: instance.Repository, Chart: instance.Chart, Version: instance.Version, Description: instance.Description, Status: string(instance.Status), StatusReason: instance.StatusReason, LastOperation: string(instance.LastOperation), LastError: instance.LastError, Revision: instance.Revision, Values: instance.Values, CreatedAt: instance.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), UpdatedAt: instance.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"), } respondJSON(w, http.StatusOK, response) } // DeleteInstance 删除实例 // @Summary 删除实例 // @Tags Instances // @Produce json // @Security BearerAuth // @Param cluster_id path string true "集群 ID" // @Param instance_id path string true "实例 ID" // @Success 204 {string} string "No Content" // @Failure 404 {object} dto.ErrorResponse // @Router /clusters/{cluster_id}/instances/{instance_id} [delete] func (h *InstanceHandler) DeleteInstance(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) instanceID := vars["instance_id"] if err := h.instanceService.DeleteInstance(r.Context(), instanceID); err != nil { respondError(w, http.StatusNotFound, "Failed to delete instance", err.Error()) return } w.WriteHeader(http.StatusNoContent) } // ListInstanceEntries 获取实例入口 // @Summary 获取实例 Service/Ingress 入口 // @Tags Instances // @Produce json // @Security BearerAuth // @Param cluster_id path string true "集群 ID" // @Param instance_id path string true "实例 ID" // @Success 200 {array} dto.InstanceEntryResponse // @Failure 404 {object} dto.ErrorResponse // @Router /clusters/{cluster_id}/instances/{instance_id}/entries [get] func (h *InstanceHandler) ListInstanceEntries(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) clusterID := vars["cluster_id"] instanceID := vars["instance_id"] entries, err := h.instanceService.ListInstanceEntries(r.Context(), clusterID, instanceID) if err != nil { status := http.StatusInternalServerError switch err { case entity.ErrInstanceNotFound: status = http.StatusNotFound case entity.ErrClusterNotFound: status = http.StatusNotFound } respondError(w, status, "Failed to list instance entries", err.Error()) return } responses := make([]*dto.InstanceEntryResponse, 0, len(entries)) for _, entry := range entries { responses = append(responses, convertInstanceEntry(entry)) } respondJSON(w, http.StatusOK, responses) } func convertInstanceEntry(entry *entity.InstanceEntry) *dto.InstanceEntryResponse { portResponses := make([]dto.InstanceEntryPortResponse, 0, len(entry.Ports)) for _, port := range entry.Ports { portResponses = append(portResponses, dto.InstanceEntryPortResponse{ Name: port.Name, Protocol: port.Protocol, Port: port.Port, TargetPort: port.TargetPort, NodePort: port.NodePort, }) } hostResponses := make([]dto.InstanceEntryHostResponse, 0, len(entry.Hosts)) for _, host := range entry.Hosts { pathResponses := make([]dto.InstanceEntryPathResponse, 0, len(host.Paths)) for _, path := range host.Paths { pathResponses = append(pathResponses, dto.InstanceEntryPathResponse{ Path: path.Path, ServiceName: path.ServiceName, ServicePort: path.ServicePort, }) } hostResponses = append(hostResponses, dto.InstanceEntryHostResponse{ Host: host.Host, Paths: pathResponses, }) } tlsResponses := make([]dto.InstanceEntryTLSResponse, 0, len(entry.TLS)) for _, tls := range entry.TLS { tlsResponses = append(tlsResponses, dto.InstanceEntryTLSResponse{ Hosts: tls.Hosts, SecretName: tls.SecretName, }) } return &dto.InstanceEntryResponse{ Kind: entry.Kind, Name: entry.Name, Namespace: entry.Namespace, Type: entry.Type, ClusterIP: entry.ClusterIP, ExternalIPs: entry.ExternalIPs, LoadBalancerIngress: entry.LoadBalancerIngress, Ports: portResponses, Hosts: hostResponses, TLS: tlsResponses, } }