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}) }