- Add Workspace domain (entity, repository, service, handler, DTO) - Add multi-tenant K8s client with tenant binding and quota management - Add K8s diagnostics client (instance diagnostics) - Add authorization middleware (authz package) - Restructure frontend to feature-based architecture (features/) - Add User Management page in configuration - Add AccessDenied page and route guards - Refactor shared components (form inputs, layout, UI) - Update Tailwind config for new design system - Add comprehensive documentation (docs/, tasks/, plans) - Improve cluster service with better kubeconfig handling - Add tests for crypto, config, helm client, tenant binding
237 lines
8.3 KiB
Go
237 lines
8.3 KiB
Go
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)
|
||
}
|
||
|
||
// 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})
|
||
}
|