Files
ocdp-go/backend/internal/adapter/input/http/rest/artifact_handler.go
Ivan087 33ddaf97db fix: scale replicas in response, K8s metrics client, quota precheck, auth tests
- Add GetMetrics method to MetricsClient interface and implement cluster metrics API
- Add QuotaPrecheck service for validating resource quotas before deployment
- Add auth DTO with role/permission models and auth handler tests
- Add instance diagnostics: mounted NFS volumes, labels, annotations in pod diagnostics
- Update workspace handler with GetWorkspace endpoint and shared-user list
- Fix monitoring handler to use correct service method name
- Add tail_lines fallback in instance handler for snake_case query params
- Update nginx config for SSE log streaming support (no buffering)
- Add comprehensive test coverage: auth_service_test, auth_handler_test,
  auth_dto_test, metrics_client_test, quota_precheck_test
- Update error messages for quota validation and instance operations
- ModifyModal: fix YAML lineWidth:0, modified keys summary, delta-only submit
- InstanceCard: correctly disable scale-minus when replicas <= 0
- SidebarLayout: add hover transition for sidebar items
- Update todo.md and lessons.md with latest fixes
2026-05-20 16:56:29 +08:00

256 lines
8.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package rest
import (
"errors"
"net/http"
"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"
)
// ArtifactHandler Artifact Handler
type ArtifactHandler struct {
artifactService *service.ArtifactService
}
// NewArtifactHandler 创建 Artifact Handler
func NewArtifactHandler(artifactService *service.ArtifactService) *ArtifactHandler {
return &ArtifactHandler{
artifactService: artifactService,
}
}
// ListRepositories 列出 Registry 中的所有 repositories
// @Summary 列出 Registry 中的所有 Repositories
// @Description 列出指定 Registry 中的所有 Repository
// @Tags Artifacts
// @Accept json
// @Produce json
// @Param registry_id path string true "Registry ID"
// @Param artifact_type query string false "Artifact type filter (chart, all)" default(chart)
// @Success 200 {object} dto.RepositoryListResponse
// @Failure 500 {object} dto.ErrorResponse
// @Router /registries/{registry_id}/repositories [get]
func (h *ArtifactHandler) ListRepositories(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
registryID := vars["registry_id"]
artifactType := r.URL.Query().Get("artifact_type")
if artifactType == "" {
artifactType = "chart"
}
repositories, err := h.artifactService.ListRepositories(r.Context(), registryID, artifactType)
if err != nil {
respondError(w, http.StatusInternalServerError, "Failed to list repositories", err.Error())
return
}
// Get registry info for URL
registry, err := h.artifactService.GetRegistry(r.Context(), registryID)
registryURL := ""
if err == nil && registry != nil {
registryURL = registry.URL
}
// Determine source and message based on repository count
source := "harbor-api"
catalogSupported := true
message := ""
if len(repositories) == 0 {
source = "unavailable"
if artifactType == "chart" {
message = "No chart repositories found in this registry"
} else {
message = "No repositories found in this registry"
}
}
response := &dto.RepositoryListResponse{
RegistryID: registryID,
RegistryURL: registryURL,
Repositories: repositories,
Total: len(repositories),
CatalogSupported: catalogSupported,
Source: source,
Message: message,
}
respondJSON(w, http.StatusOK, response)
}
// ListArtifacts 列出 repository 中的所有 artifacts返回扁平化的 Tag 数组)
// @Summary 列出 Repository 中的所有 Artifacts
// @Description 列出指定 Repository 中的所有 Artifact支持按类型过滤
// @Tags Artifacts
// @Accept json
// @Produce json
// @Param registry_id path string true "Registry ID"
// @Param repository_name path string true "Repository Name (URL encoded, e.g. charts%2Fnginx)"
// @Param media_type query string false "过滤 Artifact 类型 (all, chart, image, other)" default(all)
// @Success 200 {array} dto.TagResponse
// @Failure 500 {object} dto.ErrorResponse
// @Router /registries/{registry_id}/repositories/{repository_name}/artifacts [get]
func (h *ArtifactHandler) ListArtifacts(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
registryID := vars["registry_id"]
repositoryName := vars["repository_name"]
// 获取 mediaType 过滤参数(默认为 "all"
mediaTypeFilter := r.URL.Query().Get("media_type")
if mediaTypeFilter == "" {
mediaTypeFilter = "all"
}
artifacts, err := h.artifactService.ListArtifacts(r.Context(), registryID, repositoryName, mediaTypeFilter)
if err != nil {
respondError(w, http.StatusInternalServerError, "Failed to list artifacts", err.Error())
return
}
// 转换为前端期望的扁平化 Tag 数组
tagResponses := make([]*dto.TagResponse, 0, len(artifacts))
for _, artifact := range artifacts {
tagResponses = append(tagResponses, &dto.TagResponse{
RepositoryName: artifact.Repository,
Tag: artifact.Tag,
Type: string(artifact.Type),
MediaType: artifact.MediaType,
Size: artifact.Size,
})
}
// 直接返回数组,不包装
respondJSON(w, http.StatusOK, tagResponses)
}
// ListRepositoryTags is a compatibility alias for clients that request tags
// directly instead of the canonical artifacts endpoint.
func (h *ArtifactHandler) ListRepositoryTags(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
if vars["registry_id"] == "" {
registryID := r.URL.Query().Get("registry_id")
if registryID == "" {
registryID = r.URL.Query().Get("registryId")
}
if registryID == "" {
respondError(w, http.StatusBadRequest, "Missing registry ID", "registry_id query parameter is required")
return
}
vars["registry_id"] = registryID
r = mux.SetURLVars(r, vars)
}
h.ListArtifacts(w, r)
}
// GetArtifact 获取 artifact 详情
// @Summary 获取 Artifact 详情
// @Description 获取指定 Artifact 的详细信息
// @Tags Artifacts
// @Accept json
// @Produce json
// @Param registry_id path string true "Registry ID"
// @Param repository_name path string true "Repository Name (URL encoded)"
// @Param reference path string true "Artifact Reference (tag or digest)"
// @Success 200 {object} dto.ArtifactResponse
// @Failure 404 {object} dto.ErrorResponse
// @Router /registries/{registry_id}/repositories/{repository_name}/artifacts/{reference} [get]
func (h *ArtifactHandler) GetArtifact(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
registryID := vars["registry_id"]
repositoryName := vars["repository_name"]
reference := vars["reference"]
artifact, err := h.artifactService.GetArtifact(r.Context(), registryID, repositoryName, reference)
if err != nil {
respondError(w, http.StatusNotFound, "Artifact not found", err.Error())
return
}
response := &dto.ArtifactResponse{
RepositoryName: artifact.Repository,
Tag: artifact.Tag,
Digest: artifact.Digest,
Type: string(artifact.Type),
Size: artifact.Size,
CreatedAt: artifact.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
}
respondJSON(w, http.StatusOK, response)
}
// GetArtifactValuesSchema 获取 Helm Chart 的 values schema
// @Summary 获取 Helm Chart Values Schema
// @Description 获取 Helm Chart 的 values.schema.json (仅支持 Chart 类型)
// @Tags Artifacts
// @Accept json
// @Produce json
// @Param registry_id path string true "Registry ID"
// @Param repository_name path string true "Repository Name (URL encoded)"
// @Param reference path string true "Artifact Reference (tag or digest)"
// @Success 200 {object} dto.ValuesSchemaResponse
// @Failure 500 {object} dto.ErrorResponse
// @Router /registries/{registry_id}/repositories/{repository_name}/artifacts/{reference}/values-schema [get]
func (h *ArtifactHandler) GetArtifactValuesSchema(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
registryID := vars["registry_id"]
repositoryName := vars["repository_name"]
reference := vars["reference"]
schema, err := h.artifactService.GetValuesSchema(r.Context(), registryID, repositoryName, reference)
if err != nil {
switch {
case errors.Is(err, entity.ErrRegistryNotFound),
errors.Is(err, entity.ErrRepositoryNotFound),
errors.Is(err, entity.ErrArtifactNotFound),
errors.Is(err, entity.ErrValuesSchemaNotFound):
respondError(w, http.StatusNotFound, "Values schema not found", err.Error())
default:
respondError(w, http.StatusInternalServerError, "Failed to get values schema", err.Error())
}
return
}
response := &dto.ValuesSchemaResponse{
Schema: schema,
}
respondJSON(w, http.StatusOK, response)
}
// GetArtifactValuesYAML 获取 Helm Chart 的默认 values.yaml
// @Summary 获取 Helm Chart 默认 Values YAML
// @Description 获取 Helm Chart 包内原始 values.yaml用于高级覆盖编辑
// @Tags Artifacts
// @Accept json
// @Produce json
// @Param registry_id path string true "Registry ID"
// @Param repository_name path string true "Repository Name (URL encoded)"
// @Param reference path string true "Artifact Reference (tag or digest)"
// @Success 200 {object} dto.ValuesYAMLResponse
// @Failure 500 {object} dto.ErrorResponse
// @Router /registries/{registry_id}/repositories/{repository_name}/artifacts/{reference}/values-yaml [get]
func (h *ArtifactHandler) GetArtifactValuesYAML(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
registryID := vars["registry_id"]
repositoryName := vars["repository_name"]
reference := vars["reference"]
valuesYAML, err := h.artifactService.GetValuesYAML(r.Context(), registryID, repositoryName, reference)
if err != nil {
switch {
case errors.Is(err, entity.ErrRegistryNotFound),
errors.Is(err, entity.ErrRepositoryNotFound),
errors.Is(err, entity.ErrArtifactNotFound):
respondError(w, http.StatusNotFound, "Values YAML not found", err.Error())
default:
respondError(w, http.StatusInternalServerError, "Failed to get values YAML", err.Error())
}
return
}
respondJSON(w, http.StatusOK, &dto.ValuesYAMLResponse{ValuesYAML: valuesYAML})
}