Files
ocdp-go/backend/internal/adapter/input/http/rest/instance_handler.go
mangomqy c5e51ed069 ocdp v1
2025-11-13 02:54:06 +00:00

372 lines
12 KiB
Go

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()
// 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(
clusterID,
req.Name,
req.Namespace,
req.RegistryID,
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,
}
}